diff --git a/src/Automate/Command/BaseCommand.php b/src/Automate/Command/BaseCommand.php index b5971d2..b0167ec 100644 --- a/src/Automate/Command/BaseCommand.php +++ b/src/Automate/Command/BaseCommand.php @@ -17,7 +17,6 @@ use Automate\Model\Project; use Automate\VariableResolver; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; abstract class BaseCommand extends Command @@ -26,11 +25,7 @@ abstract class BaseCommand extends Command protected function getLogger(SymfonyStyle $io): LoggerInterface { - $verbosity = $io->getVerbosity() > OutputInterface::VERBOSITY_NORMAL - ? LoggerInterface::VERBOSITY_DEBUG - : LoggerInterface::VERBOSITY_NORMAL; - - return new ConsoleLogger($io, $verbosity); + return new ConsoleLogger($io); } protected function resolveVariables(SymfonyStyle $io, Project $project, Platform $platform): void diff --git a/src/Automate/Command/CheckCommand.php b/src/Automate/Command/CheckCommand.php index 0afb4af..a342919 100644 --- a/src/Automate/Command/CheckCommand.php +++ b/src/Automate/Command/CheckCommand.php @@ -11,8 +11,9 @@ namespace Automate\Command; -use Automate\Context\SSHContext; use Automate\Loader; +use Automate\Workflow\Context; +use Automate\Workflow\Session; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -45,11 +46,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $logger = $this->getLogger($io); try { - $context = new SSHContext($project, $platform, $logger, $platform->getDefaultBranch()); + $context = new Context($project, $platform, $logger, $platform->getDefaultBranch()); $context->connect(); $logger->section('Check git access'); - $context->run('git ls-remote '.$project->getRepository(), false, null, false); + $context->exec(function (Session $session) use ($project) { + $session->exec('git ls-remote '.$project->getRepository(), false); + }); } catch (\Exception $exception) { $io->error($exception->getMessage()); diff --git a/src/Automate/Command/DeployCommand.php b/src/Automate/Command/DeployCommand.php index 63ab26d..effa33b 100644 --- a/src/Automate/Command/DeployCommand.php +++ b/src/Automate/Command/DeployCommand.php @@ -11,9 +11,9 @@ namespace Automate\Command; -use Automate\Context\SSHContext; use Automate\Loader; use Automate\Model\Platform; +use Automate\Workflow\Context; use Automate\Workflow\Deployer; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ['Version', $input->getArgument('gitRef') ?: $platform->getDefaultBranch()], ]); - $context = new SSHContext($project, $platform, $logger, $gitRef, $input->getOption('force')); + $context = new Context($project, $platform, $logger, $gitRef, $input->getOption('force')); $workflow = new Deployer($context); if (!$workflow->deploy()) { diff --git a/src/Automate/Context/AbstractContext.php b/src/Automate/Context/AbstractContext.php deleted file mode 100644 index e1bbcb4..0000000 --- a/src/Automate/Context/AbstractContext.php +++ /dev/null @@ -1,154 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Context; - -use Automate\Logger\LoggerInterface; -use Automate\Model\Platform; -use Automate\Model\Project; -use Automate\Model\Server; -use Automate\Session\SessionInterface; - -abstract class AbstractContext implements ContextInterface -{ - protected ?string $releaseId = null; - - protected ?bool $isDeployed = null; - - public function __construct( - protected Project $project, - protected Platform $platform, - protected LoggerInterface $logger, - protected ?string $gitRef = null, - protected bool $force = false, - ) { - } - - abstract public function connect(): void; - - abstract public function getSession(Server $server): SessionInterface; - - public function getGitRef(): ?string - { - return $this->gitRef; - } - - public function getProject(): Project - { - return $this->project; - } - - public function getPlatform(): Platform - { - return $this->platform; - } - - public function getLogger(): LoggerInterface - { - return $this->logger; - } - - public function isDeployed(): ?bool - { - return $this->isDeployed; - } - - public function setDeployed(bool $isDeployed): static - { - $this->isDeployed = $isDeployed; - - return $this; - } - - public function isForce(): bool - { - return $this->force; - } - - public function setForce(bool $force): static - { - $this->force = $force; - - return $this; - } - - public function getReleaseId(): string - { - if (null === $this->releaseId) { - $date = new \DateTime(); - - $this->releaseId = sprintf( - '%s.%s.%s-%s%s.%s', - $date->format('Y'), - $date->format('m'), - $date->format('d'), - $date->format('H'), - $date->format('i'), - random_int(100, 999) - ); - } - - return $this->releaseId; - } - - public function run(string $command, bool $verbose = false, ?array $specificServers = null, bool $addWorkingDir = true): void - { - $servers = $this->platform->getServers(); - - foreach ($servers as $server) { - if ($specificServers && !in_array($server->getName(), $specificServers)) { - continue; - } - - $this->logger->command($command, $verbose); - $this->doRun($server, $command, $addWorkingDir, $verbose); - } - } - - public function doRun(Server $server, string $command, bool $addWorkingDir = true, bool $verbose = false): ?string - { - $realCommand = $addWorkingDir ? sprintf('cd %s; %s', $this->getReleasePath($server), $command) : $command; - - $response = $this->getSession($server)->run($realCommand); - - if ('' !== $response) { - $this->logger->response($response, $server->getName(), $verbose); - } - - return $response; - } - - public function getReleasePath(Server $server): string - { - return $this->getReleasesPath($server).'/'.$this->getReleaseId(); - } - - public function getReleasesPath(Server $server): string - { - return $server->getPath().'/releases'; - } - - public function getSharedPath(Server $server): string - { - $serverSharedPath = $server->getSharedPath(); - - // if the shared path is not configured on the server configuration - if (null === $serverSharedPath) { - $serverSharedPath = $server->getPath().'/shared'; - } - - return $serverSharedPath; - } - - public function getCurrentPath(Server $server): string - { - return $server->getPath().'/current'; - } -} diff --git a/src/Automate/Context/ContextInterface.php b/src/Automate/Context/ContextInterface.php deleted file mode 100644 index c78df8e..0000000 --- a/src/Automate/Context/ContextInterface.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Context; - -use Automate\Logger\LoggerInterface; -use Automate\Model\Platform; -use Automate\Model\Project; -use Automate\Model\Server; -use Automate\Session\SessionInterface; - -interface ContextInterface -{ - public function connect(): void; - - public function getSession(Server $server): SessionInterface; - - public function getGitRef(): ?string; - - public function getProject(): Project; - - public function getPlatform(): Platform; - - public function getLogger(): LoggerInterface; - - public function isDeployed(): ?bool; - - public function setDeployed(bool $isDeployed): static; - - public function isForce(): bool; - - public function setForce(bool $force): static; - - public function getReleaseId(): string; - - /** - * @param ?array $specificServers - */ - public function run(string $command, bool $verbose = false, ?array $specificServers = null, bool $addWorkingDir = true): void; - - public function doRun(Server $server, string $command, bool $addWorkingDir = true, bool $verbose = false): ?string; - - public function getReleasePath(Server $server): string; - - public function getReleasesPath(Server $server): string; - - public function getSharedPath(Server $server): string; - - public function getCurrentPath(Server $server): string; -} diff --git a/src/Automate/Context/SSHContext.php b/src/Automate/Context/SSHContext.php deleted file mode 100644 index c4fdcfd..0000000 --- a/src/Automate/Context/SSHContext.php +++ /dev/null @@ -1,59 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Context; - -use Automate\Logger\LoggerInterface; -use Automate\Model\Platform; -use Automate\Model\Project; -use Automate\Model\Server; -use Automate\Session\SessionInterface; -use Automate\SessionFactory; - -class SSHContext extends AbstractContext -{ - /** - * @var SessionInterface[] - */ - protected array $sessions = []; - - protected SessionFactory $sessionFactory; - - public function __construct(Project $project, Platform $platform, LoggerInterface $logger, ?string $gitRef = null, bool $force = false) - { - parent::__construct($project, $platform, $logger, $gitRef, $force); - $this->sessionFactory = new SessionFactory(); - } - - public function connect(): void - { - $this->logger->section('Remote servers connection'); - - foreach ($this->platform->getServers() as $server) { - $session = $this->sessionFactory->create($server); - $this->logger->response('Connection successful', $server->getName(), true); - $this->sessions[$server->getName()] = $session; - } - } - - public function getSession(Server $server): SessionInterface - { - if (!isset($this->sessions[$server->getName()])) { - throw new \RuntimeException('Unable to find session'); - } - - return $this->sessions[$server->getName()]; - } - - public function setSessionFactory(SessionFactory $sessionFactory): void - { - $this->sessionFactory = $sessionFactory; - } -} diff --git a/src/Automate/Event/DeployEvent.php b/src/Automate/Event/DeployEvent.php index 92aeb4a..79eea2a 100644 --- a/src/Automate/Event/DeployEvent.php +++ b/src/Automate/Event/DeployEvent.php @@ -11,17 +11,17 @@ namespace Automate\Event; -use Automate\Context\ContextInterface; +use Automate\Workflow\Context; use Symfony\Contracts\EventDispatcher\Event; class DeployEvent extends Event { public function __construct( - private readonly ContextInterface $context, + private readonly Context $context, ) { } - public function getContext(): ContextInterface + public function getContext(): Context { return $this->context; } diff --git a/src/Automate/Event/FailedDeployEvent.php b/src/Automate/Event/FailedDeployEvent.php index 82a1449..9092c15 100644 --- a/src/Automate/Event/FailedDeployEvent.php +++ b/src/Automate/Event/FailedDeployEvent.php @@ -11,11 +11,11 @@ namespace Automate\Event; -use Automate\Context\ContextInterface; +use Automate\Workflow\Context; class FailedDeployEvent extends DeployEvent { - public function __construct(ContextInterface $context, private readonly \Exception $exception) + public function __construct(Context $context, private readonly \Exception $exception) { parent::__construct($context); } diff --git a/src/Automate/Listener/ClearListener.php b/src/Automate/Listener/ClearListener.php index f3e989f..9507de3 100644 --- a/src/Automate/Listener/ClearListener.php +++ b/src/Automate/Listener/ClearListener.php @@ -15,6 +15,7 @@ use Automate\Event\DeployEvents; use Automate\Event\FailedDeployEvent; use Automate\Model\Server; +use Automate\Workflow\Session; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ClearListener implements EventSubscriberInterface @@ -29,41 +30,36 @@ public static function getSubscribedEvents(): array } /** - * Move current release to /releases/failed. + * remove the lasted failed release. */ - public function moveFailedRelease(FailedDeployEvent $event): void + public function removeFailedRelease(DeployEvent $event): void { $context = $event->getContext(); - // not move if deploy - if (!$context->isDeployed()) { - foreach ($context->getPlatform()->getServers() as $server) { - if (null !== $context->getReleasePath($server)) { - $session = $context->getSession($server); - - $release = $context->getReleasePath($server); - $failed = $this->getFailedPath($server); - - $context->getLogger()->response(sprintf('move release to %s', $failed), $server->getName(), true); - - $session->mv($release, $failed); - } + $context->exec(function (Session $session): void { + if ($session->exists($this->getFailedPath($session->getServer()))) { + $session->rm($this->getFailedPath($session->getServer()), true); } - } + }); } /** - * remove the lasted failed release. + * Move current release to /releases/failed. */ - public function removeFailedRelease(DeployEvent $event): void + public function moveFailedRelease(FailedDeployEvent $event): void { $context = $event->getContext(); - foreach ($context->getPlatform()->getServers() as $server) { - $session = $context->getSession($server); - if ($session->exists($this->getFailedPath($server))) { - $session->rm($this->getFailedPath($server), true); - } + // not move if deploy + if (!$context->isDeployed()) { + $context->exec(function (Session $session) use ($context): void { + $release = $session->getReleasePath(); + $failed = $this->getFailedPath($session->getServer()); + + $context->getLogger()->info(sprintf('move release to %s', $failed), $session->getServer()); + + $session->mv($release, $failed); + }); } } @@ -76,28 +72,23 @@ public function clearReleases(DeployEvent $event): void $context->getLogger()->section('Clear olds releases'); - foreach ($context->getPlatform()->getServers() as $server) { - $session = $context->getSession($server); - - $releases = $session->listDirectory($context->getReleasesPath($server)); + $context->exec(static function (Session $session) use ($context): void { + $releases = $session->listDirectory($session->getReleasesPath()); $releases = array_map('trim', $releases); rsort($releases); - // ignore others folders $releases = array_filter($releases, static fn ($release): int|false => preg_match('/\d{4}\.\d{2}\.\d{2}-\d{4}\./', (string) $release)); - $keep = $context->getPlatform()->getMaxReleases(); - while ($keep > 0) { array_shift($releases); --$keep; } foreach ($releases as $release) { - $context->getLogger()->response('rm -R '.$release, $server->getName(), true); + $context->getLogger()->info('Remove old release : '.$release, $session->getServer()); $session->rm($release, true); } - } + }); } private function getFailedPath(Server $server): string diff --git a/src/Automate/Listener/LockListener.php b/src/Automate/Listener/LockListener.php index eaa6518..68a8635 100644 --- a/src/Automate/Listener/LockListener.php +++ b/src/Automate/Listener/LockListener.php @@ -14,10 +14,14 @@ use Automate\Event\DeployEvent; use Automate\Event\DeployEvents; use Automate\Model\Server; +use Automate\Workflow\Session; +use RectorPrefix202403\Symfony\Component\Filesystem\Path; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class LockListener implements EventSubscriberInterface { + public const string LOCK_FILE = 'automate.lock'; + private bool $hasLock = false; public static function getSubscribedEvents(): array @@ -37,17 +41,13 @@ public function initLockFile(DeployEvent $event): void { $context = $event->getContext(); - foreach ($context->getPlatform()->getServers() as $server) { - $session = $context->getSession($server); - if ($session->exists($this->getLockFilePath($server)) && !$context->isForce()) { + $context->exec(function (Session $session) use ($context): void { + if ($session->exists($this->getLockFilePath($session->getServer())) && !$context->isForce()) { throw new \RuntimeException('A deployment is already in progress'); } - } - foreach ($context->getPlatform()->getServers() as $server) { - $session = $context->getSession($server); - $session->touch($this->getLockFilePath($server)); - } + $session->touch($this->getLockFilePath($session->getServer())); + }); $this->hasLock = true; } @@ -60,18 +60,17 @@ public function clearLockFile(DeployEvent $event): void $context = $event->getContext(); if ($this->hasLock) { - foreach ($context->getPlatform()->getServers() as $server) { - $session = $context->getSession($server); - $session->rm($this->getLockFilePath($server)); - } + $context->exec(function (Session $session): void { + $session->rm($this->getLockFilePath($session->getServer())); + }); } } /** * Get lock file path. */ - public function getLockFilePath(Server $server): string + private function getLockFilePath(Server $server): string { - return $server->getPath().'/automate.lock'; + return Path::join($server->getPath(), self::LOCK_FILE); } } diff --git a/src/Automate/Logger/ConsoleLogger.php b/src/Automate/Logger/ConsoleLogger.php index a3f6590..1c2135c 100644 --- a/src/Automate/Logger/ConsoleLogger.php +++ b/src/Automate/Logger/ConsoleLogger.php @@ -11,14 +11,13 @@ namespace Automate\Logger; -use Symfony\Component\Console\Output\OutputInterface; +use Automate\Model\Server; use Symfony\Component\Console\Style\SymfonyStyle; class ConsoleLogger implements LoggerInterface { public function __construct( private readonly SymfonyStyle $io, - private readonly int $verbosity = self::VERBOSITY_NORMAL, ) { } @@ -27,22 +26,27 @@ public function section(string $title): void $this->io->block($title, '*', 'fg=white;bg=blue', ' ', true); } - public function command(string $name, bool $verbose = false): void + public function command(string $name): void { - if ($verbose || $this->verbosity > OutputInterface::VERBOSITY_NORMAL) { - $this->io->text(sprintf('%s', $name)); + $this->io->text(sprintf('%s', $name)); + } + + public function result(string $response, Server $server): void + { + if (substr_count($response, "\n") > 0) { + $this->io->text(sprintf('[%s]', $server->getName())); + $this->io->text($response); + } else { + $this->io->text(sprintf('[%s] %s', $server->getName(), $response)); } } - public function response(string $response, string $server, bool $verbose = false): void + public function info(string $text, ?Server $server): void { - if ($verbose || $this->verbosity > OutputInterface::VERBOSITY_NORMAL) { - if (substr_count($response, "\n") > 0) { - $this->io->text(sprintf('[%s]', $server)); - $this->io->text($response); - } else { - $this->io->text(sprintf('[%s] %s', $server, $response)); - } + if ($server instanceof Server) { + $this->io->text(sprintf('[%s] %s', $server->getName(), $text)); + } else { + $this->io->text($text); } } diff --git a/src/Automate/Logger/LoggerInterface.php b/src/Automate/Logger/LoggerInterface.php index dd2d97d..8752249 100644 --- a/src/Automate/Logger/LoggerInterface.php +++ b/src/Automate/Logger/LoggerInterface.php @@ -11,29 +11,17 @@ namespace Automate\Logger; +use Automate\Model\Server; + interface LoggerInterface { - public const int VERBOSITY_NORMAL = 1; - - public const int VERBOSITY_DEBUG = 10; - - /** - * Section title. - */ public function section(string $title): void; - /** - * Run command. - */ - public function command(string $name, bool $verbose = false): void; + public function command(string $name): void; + + public function result(string $response, Server $server): void; - /** - * Remote response. - */ - public function response(string $response, string $server, bool $verbose = false): void; + public function info(string $text, ?Server $server): void; - /** - * Remote error. - */ public function error(string $message): void; } diff --git a/src/Automate/Session/AbstractSession.php b/src/Automate/Session/AbstractSession.php deleted file mode 100644 index 3ed1603..0000000 --- a/src/Automate/Session/AbstractSession.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Session; - -abstract class AbstractSession implements SessionInterface -{ - abstract public function run(string $command): string; - - public function mkdir(string $path, bool $recursive = false): void - { - $command = sprintf('mkdir%s %s', $recursive ? ' -p' : '', $path); - - $this->run($command); - } - - public function mv(string $from, string $to): void - { - if (!$this->exists(dirname($to))) { - $this->run(sprintf('mkdir -p %s', dirname($to))); - } - - $this->run(sprintf('mv %s %s', $from, $to)); - } - - public function rm(string $path, bool $recursive = false): void - { - $this->run(sprintf('rm%s %s', $recursive ? ' -R' : '', $path)); - } - - public function exists(string $path): bool - { - if ('Y' === trim((string) $this->run(sprintf('if test -d "%s"; then echo "Y";fi', $path)))) { - return true; - } - - return 'Y' === trim((string) $this->run(sprintf('if test -f "%s"; then echo "Y";fi', $path))); - } - - public function symlink(string $target, string $link): void - { - $this->run(sprintf('ln -sfn %s %s', $target, $link)); - } - - public function touch(string $path): void - { - $this->run(sprintf('mkdir -p %s', dirname($path))); - $this->run(sprintf('touch %s', $path)); - } - - public function listDirectory(string $path): array - { - $rs = (string) $this->run(sprintf('find %s -maxdepth 1 -mindepth 1 -type d', $path)); - - return explode("\n", trim($rs)); - } -} diff --git a/src/Automate/Session/SSHSession.php b/src/Automate/Session/SSHSession.php deleted file mode 100644 index 3186eb3..0000000 --- a/src/Automate/Session/SSHSession.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Session; - -use phpseclib3\Net\SSH2; - -class SSHSession extends AbstractSession -{ - /** - * Session constructor. - */ - public function __construct( - private readonly SSH2 $ssh, - ) { - $this->ssh->setTimeout(0); - } - - public function run(string $command): string - { - $rs = (string) $this->ssh->exec($command); - - if (0 !== $this->ssh->getExitStatus()) { - throw new \RuntimeException($rs); - } - - return $rs; - } -} diff --git a/src/Automate/Session/SessionInterface.php b/src/Automate/Session/SessionInterface.php deleted file mode 100644 index b347d35..0000000 --- a/src/Automate/Session/SessionInterface.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Automate\Session; - -interface SessionInterface -{ - /** - * Execute e command. - */ - public function run(string $command): string; - - /** - * Creates a directory. - * - * @param string $path The name of the new directory - * @param bool $recursive Whether to automatically create any required - * parent directory - */ - public function mkdir(string $path, bool $recursive = false): void; - - /** - * Move a file or a directory. - * - * @param string $from The current name of the directory or file - * @param string $to The new name of the directory or file - */ - public function mv(string $from, string $to): void; - - /** - * Removes a directory or a file. - * - * @param string $path The directory or file that is being removed - */ - public function rm(string $path, bool $recursive = false): void; - - /** - * Indicates whether the specified distant file or directory exists. - * - * @param string $path The distant filename ou directory - */ - public function exists(string $path): bool; - - /** - * Creates a symlink. - * - * @param string $target The target of the symlink - * @param string $link The path of the link - */ - public function symlink(string $target, string $link): void; - - /** - * Touch file. - * - * @param string $path FIle path - */ - public function touch(string $path): void; - - /** - * Lists directories of the specified path. - * - * @return string[] - */ - public function listDirectory(string $path): array; -} diff --git a/src/Automate/Workflow/Context.php b/src/Automate/Workflow/Context.php new file mode 100644 index 0000000..599c2e5 --- /dev/null +++ b/src/Automate/Workflow/Context.php @@ -0,0 +1,116 @@ +generateReleaseId(); + } + + public function connect(): void + { + foreach ($this->platform->getServers() as $server) { + $this->sessions[$server->getName()] = SessionFactory::create($this, $server); + } + } + + /** + * @param string[] $serversList + */ + public function exec(string|callable $command, ?array $serversList = null, bool $addWorkingDir = true): void + { + foreach ($this->sessions as $session) { + if ($serversList && !in_array($session->getServer()->getName(), $serversList)) { + continue; + } + + if (is_string($command)) { + $this->logger->command($command); + $result = $session->exec($command, $addWorkingDir); + + if ('' !== $result) { + $this->logger->result($result, $session->getServer()); + } + } else { + $command($session); + } + } + } + + public function getProject(): Project + { + return $this->project; + } + + public function getPlatform(): Platform + { + return $this->platform; + } + + public function getLogger(): LoggerInterface + { + return $this->logger; + } + + public function getGitRef(): ?string + { + return $this->gitRef; + } + + public function isForce(): bool + { + return $this->force; + } + + public function isDeployed(): ?bool + { + return $this->isDeployed; + } + + public function setDeployed(bool $isDeployed): static + { + $this->isDeployed = $isDeployed; + + return $this; + } + + private function generateReleaseId(): void + { + $date = new \DateTime(); + + $this->releaseId = sprintf( + '%s.%s.%s-%s%s.%s', + $date->format('Y'), + $date->format('m'), + $date->format('d'), + $date->format('H'), + $date->format('i'), + random_int(100, 999) + ); + } + + public function getReleaseId(): string + { + return $this->releaseId; + } +} diff --git a/src/Automate/Workflow/Deployer.php b/src/Automate/Workflow/Deployer.php index 6addc5b..e5845de 100644 --- a/src/Automate/Workflow/Deployer.php +++ b/src/Automate/Workflow/Deployer.php @@ -11,22 +11,20 @@ namespace Automate\Workflow; -use Automate\Context\ContextInterface; use Automate\DispatcherFactory; use Automate\Event\DeployEvent; use Automate\Event\DeployEvents; use Automate\Event\FailedDeployEvent; use Automate\Model\Command; -use Automate\Model\Server; +use Symfony\Component\Filesystem\Path; /** * Deployment workflow. */ readonly class Deployer { - public function __construct( - private ContextInterface $context, - ) { + public function __construct(private Context $context) + { } /** @@ -88,15 +86,15 @@ private function deployWithGit(): void 'git clone %s -q --recursive .', $this->context->getProject()->getRepository()); } - $this->context->run($clone, true); + $this->context->exec($clone); $gitRef = $this->context->getGitRef(); if (null !== $gitRef) { $listTagsCommand = sprintf("git tag --list '%s'", $gitRef); - $this->context->getLogger()->command($listTagsCommand); - foreach ($this->context->getPlatform()->getServers() as $server) { - if (null !== $this->context->doRun($server, $listTagsCommand, true)) { + + $this->context->exec(function (Session $session) use ($listTagsCommand, $gitRef): void { + if ('' !== $session->exec($listTagsCommand)) { // checkout a tag $command = sprintf('git checkout tags/%s', $gitRef); } else { @@ -104,9 +102,9 @@ private function deployWithGit(): void $command = sprintf('git checkout %s', $gitRef); } - $this->context->getLogger()->command($command, true); - $this->context->doRun($server, $command, true, true); - } + $this->context->getLogger()->command($command); + $this->context->exec($command); + }); } } @@ -121,7 +119,7 @@ private function runHooks(array $commands, string $name): void $this->context->getLogger()->section($name); foreach ($commands as $command) { if ('' !== $command->getCmd() && !str_starts_with(trim((string) $command->getCmd()), '#')) { - $this->context->run($command->getCmd(), true, $command->getOnly()); + $this->context->exec($command->getCmd(), $command->getOnly()); } } } @@ -137,27 +135,25 @@ private function initShared(): void if (count($folders) || count($files)) { $this->context->getLogger()->section('Setting up shared items'); - foreach ($this->context->getPlatform()->getServers() as $server) { + $this->context->exec(function (Session $session) use ($folders, $files): void { foreach ($folders as $folder) { - $this->doShared($folder, $server, true); + $this->doShared($folder, $session, true); } foreach ($files as $file) { - $this->doShared($file, $server, false); + $this->doShared($file, $session, false); } - } + }); } } - private function doShared(string $path, Server $server, bool $isDirectory): void + private function doShared(string $path, Session $session, bool $isDirectory): void { - $session = $this->context->getSession($server); - $path = trim($path); $path = ltrim($path, '/'); - $releasePath = $this->context->getReleasePath($server).'/'.$path; - $sharedPath = $this->context->getSharedPath($server).'/'.$path; + $releasePath = Path::join($session->getReleasePath(), $path); + $sharedPath = Path::join($session->getSharedPath(), $path); // For the first deployment : create shared form source if (!$session->exists($sharedPath) && $session->exists($releasePath)) { @@ -185,8 +181,8 @@ private function doShared(string $path, Server $server, bool $isDirectory): void } // create symlink - $this->context->getLogger()->response(sprintf('%s --> %s', $releasePath, $sharedPath), $server->getName(), true); $session->symlink($sharedPath, $releasePath); + $this->context->getLogger()->info(sprintf('%s --> %s', $releasePath, $sharedPath), $session->getServer()); } /** @@ -196,13 +192,13 @@ private function activateSymlink(): void { $this->context->getLogger()->section('Publish new release'); - foreach ($this->context->getPlatform()->getServers() as $server) { - $currentPath = $this->context->getCurrentPath($server); - $releasePath = $this->context->getReleasePath($server); + $this->context->exec(function (Session $session): void { + $currentPath = $session->getCurrentPath(); + $releasePath = $session->getReleasePath(); - $this->context->getLogger()->response(sprintf('%s --> %s', $currentPath, $releasePath), $server->getName(), true); - $this->context->getSession($server)->symlink($releasePath, $currentPath); - } + $session->symlink($releasePath, $currentPath); + $this->context->getLogger()->info(sprintf('%s --> %s', $currentPath, $releasePath), $session->getServer()); + }); } /** @@ -210,8 +206,8 @@ private function activateSymlink(): void */ private function createReleaseDirectory(): void { - foreach ($this->context->getPlatform()->getServers() as $server) { - $this->context->getSession($server)->mkdir($this->context->getReleasePath($server), true); - } + $this->context->exec(static function (Session $session): void { + $session->mkdir($session->getReleasePath()); + }); } } diff --git a/src/Automate/Workflow/Session.php b/src/Automate/Workflow/Session.php new file mode 100644 index 0000000..3986738 --- /dev/null +++ b/src/Automate/Workflow/Session.php @@ -0,0 +1,119 @@ +getReleasePath(), $command) : $command; + + $rs = (string) $this->sftp->exec($command); + + if (0 !== $this->sftp->getExitStatus()) { + throw new \RuntimeException($rs); + } + + return $rs; + } + + public function mkdir(string $path, bool $recursive = false): void + { + $command = sprintf('mkdir%s %s', $recursive ? ' -p' : '', $path); + + $this->exec($command, false); + } + + public function mv(string $from, string $to): void + { + if (!$this->exists(dirname($to))) { + $this->exec(sprintf('mkdir -p %s', dirname($to))); + } + + $this->exec(sprintf('mv %s %s', $from, $to), false); + } + + public function rm(string $path, bool $recursive = false): void + { + $this->exec(sprintf('rm%s %s', $recursive ? ' -R' : '', $path), false); + } + + public function exists(string $path): bool + { + if ('Y' === trim($this->exec(sprintf('if test -d "%s"; then echo "Y";fi', $path), false))) { + return true; + } + + return 'Y' === trim($this->exec(sprintf('if test -f "%s"; then echo "Y";fi', $path), false)); + } + + public function symlink(string $target, string $link): void + { + $this->exec(sprintf('ln -sfn %s %s', $target, $link), false); + } + + public function touch(string $path): void + { + $this->exec(sprintf('mkdir -p %s', dirname($path)), false); + $this->exec(sprintf('touch %s', $path), false); + } + + /** + * @return string[] + */ + public function listDirectory(string $path): array + { + $rs = $this->exec(sprintf('find %s -maxdepth 1 -mindepth 1 -type d', $path), false); + + return explode("\n", trim($rs)); + } + + public function getServer(): Server + { + return $this->server; + } + + public function getReleasePath(): string + { + return Path::join($this->getReleasesPath(), $this->context->getReleaseId()); + } + + public function getReleasesPath(): string + { + return Path::join($this->server->getPath(), self::RELEASES_FOLDER); + } + + public function getSharedPath(): string + { + $serverSharedPath = $this->server->getSharedPath(); + + // if the shared path is not configured on the server configuration + if (null === $serverSharedPath) { + $serverSharedPath = Path::join($this->server->getPath(), self::SHARED_FOLDER); + } + + return $serverSharedPath; + } + + public function getCurrentPath(): string + { + return Path::join($this->server->getPath(), self::CURRENT_FOLDER); + } +} diff --git a/src/Automate/SessionFactory.php b/src/Automate/Workflow/SessionFactory.php similarity index 75% rename from src/Automate/SessionFactory.php rename to src/Automate/Workflow/SessionFactory.php index a444643..aeff4a8 100644 --- a/src/Automate/SessionFactory.php +++ b/src/Automate/Workflow/SessionFactory.php @@ -9,24 +9,17 @@ * file that was distributed with this source code. */ -namespace Automate; +namespace Automate\Workflow; use Automate\Model\Server; -use Automate\Session\SessionInterface; -use Automate\Session\SSHSession; use phpseclib3\Crypt\PublicKeyLoader; -use phpseclib3\Net\SSH2; +use phpseclib3\Net\SFTP; class SessionFactory { - /** - * Create session. - * - * @throws \Exception - */ - public function create(Server $server): SessionInterface + public static function create(Context $context, Server $server): Session { - $ssh = new SSH2($server->getHost(), $server->getPort()); + $ssh = new SFTP($server->getHost(), $server->getPort()); // Connection with ssh key and optional if (null !== $server->getSshKey()) { @@ -42,6 +35,6 @@ public function create(Server $server): SessionInterface throw new \Exception(sprintf('[%s] Invalid user or password', $server->getName())); } - return new SSHSession($ssh); + return new Session($context, $server, $ssh); } }