Change structure.

This commit is contained in:
Tadas Gliaubicas
2017-01-10 14:31:57 +02:00
parent c883d4a6bc
commit bd6a3d5808
85 changed files with 9 additions and 7 deletions
+69
View File
@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command;
use Mage\Utils;
use Mage\Runtime\Runtime;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\Command;
/**
* Abstract base class for Magallanes Commands
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
abstract class AbstractCommand extends Command
{
/**
* @var int
*/
protected $statusCode = 0;
/**
* @var Runtime Current Runtime instance
*/
protected $runtime;
/**
* Set the Runtime configuration
*
* @param Runtime $runtime Runtime container
* @return AbstractCommand
*/
public function setRuntime(Runtime $runtime)
{
$this->runtime = $runtime;
return $this;
}
/**
* Logs a message
*
* @param string $message
* @param string $level
*/
public function log($message, $level = LogLevel::DEBUG)
{
$this->runtime->log($message, $level);
}
/**
* Get the Human friendly Stage name
*
* @return string
*/
protected function getStageName()
{
$utils = new Utils();
return $utils->getStageName($this->runtime->getStage());
}
}
@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Config;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* Command for Dumping the Configuration
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class DumpCommand extends AbstractCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('config:dump')
->setDescription('Dumps the Magallanes configuration')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
$output->writeln(sprintf('<comment>%s</comment>', var_export($this->runtime->getConfiguration(), true)));
$output->writeln('');
$output->writeln('Finished <fg=blue>Magallanes</>');
return 0;
}
}
@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Config;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Mage\Command\AbstractCommand;
/**
* Command for listing all the Environments
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class EnvironmentsCommand extends AbstractCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('config:environments')
->setDescription('List all Magallanes configured Environments')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
$table = new Table($output);
$table->setHeaders(['Environment', 'User', 'Branch', 'Hosts']);
$configuration = $this->runtime->getConfigOptions('environments');
foreach ($configuration as $environment => $config) {
$row = [$environment];
$row[] = (isset($config['user']) ? $config['user'] : '-');
$row[] = (isset($config['branch']) ? $config['branch'] : '-');
$row[] = (isset($config['hosts']) ? implode(PHP_EOL, $config['hosts']) : '-');
$table->addRow($row);
}
$table->render();
$output->writeln('');
$output->writeln('Finished <fg=blue>Magallanes</>');
return 0;
}
}
+307
View File
@@ -0,0 +1,307 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn;
use Mage\Runtime\Exception\RuntimeException;
use Mage\Runtime\Runtime;
use Mage\Task\ExecuteOnRollbackInterface;
use Mage\Task\AbstractTask;
use Mage\Task\Exception\ErrorException;
use Mage\Task\Exception\SkipException;
use Mage\Task\TaskFactory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* The Deployment Command
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class DeployCommand extends AbstractCommand
{
/**
* @var TaskFactory
*/
protected $taskFactory;
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('deploy')
->setDescription('Deploy code to hosts')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Force to switch to a branch other than the one defined', false)
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return integer
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
if ($this->runtime->getEnvironmentConfig('releases', false)) {
$this->runtime->generateReleaseId();
$output->writeln(sprintf(' Release ID: <fg=green>%s</>', $this->runtime->getReleaseId()));
$this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
}
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
if ($input->getOption('branch') !== false) {
$this->runtime->setEnvironmentConfig('branch', $input->getOption('branch'));
}
if ($this->runtime->getEnvironmentConfig('branch', false)) {
$output->writeln(sprintf(' Branch: <fg=green>%s</>', $this->runtime->getEnvironmentConfig('branch')));
}
$output->writeln('');
$this->taskFactory = new TaskFactory($this->runtime);
$this->runDeployment($output);
} catch (RuntimeException $exception) {
$output->writeln('');
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
$output->writeln('');
$this->statusCode = 7;
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return $this->statusCode;
}
/**
* Run the Deployment Process
*
* @param OutputInterface $output
* @throws RuntimeException
*/
protected function runDeployment(OutputInterface $output)
{
// Run "Pre Deploy" Tasks
$this->runtime->setStage(Runtime::PRE_DEPLOY);
$preDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('branch', false) && !$this->runtime->inRollback()) {
if (!in_array('git/change-branch', $preDeployTasks)) {
array_unshift($preDeployTasks, 'git/change-branch');
}
}
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/targz/prepare', $preDeployTasks)) {
array_push($preDeployTasks, 'deploy/targz/prepare');
}
}
if (!$this->runTasks($output, $preDeployTasks)) {
throw new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
}
// Run "On Deploy" Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping On Deploy tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::ON_DEPLOY);
$onDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false)) {
if (!in_array('deploy/targz/copy', $onDeployTasks) && !$this->runtime->inRollback()) {
array_unshift($onDeployTasks, 'deploy/targz/copy');
}
if (!in_array('deploy/release/prepare', $onDeployTasks) && !$this->runtime->inRollback()) {
array_unshift($onDeployTasks, 'deploy/release/prepare');
}
} else {
if (!in_array('deploy/rsync', $onDeployTasks) && !$this->runtime->inRollback()) {
array_unshift($onDeployTasks, 'deploy/rsync');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $onDeployTasks)) {
$this->runtime->setWorkingHost(null);
throw new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
}
$this->runtime->setWorkingHost(null);
}
}
// Run "On Release" Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping On Release tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::ON_RELEASE);
$onReleaseTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false)) {
if (!in_array('deploy/release', $onReleaseTasks)) {
array_unshift($onReleaseTasks, 'deploy/release');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $onReleaseTasks)) {
$this->runtime->setWorkingHost(null);
throw new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
}
$this->runtime->setWorkingHost(null);
}
}
// Run "Post Release" Tasks
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln(' No hosts defined, skipping Post Release tasks');
$output->writeln('');
} else {
$this->runtime->setStage(Runtime::POST_RELEASE);
$postReleaseTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/release/cleanup', $postReleaseTasks)) {
array_unshift($postReleaseTasks, 'deploy/release/cleanup');
}
}
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
if (!$this->runTasks($output, $postReleaseTasks)) {
$this->runtime->setWorkingHost(null);
throw new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
}
$this->runtime->setWorkingHost(null);
}
}
// Run "Post Deploy" Tasks
$this->runtime->setStage(Runtime::POST_DEPLOY);
$postDeployTasks = $this->runtime->getTasks();
if ($this->runtime->getEnvironmentConfig('releases', false) && !$this->runtime->inRollback()) {
if (!in_array('deploy/targz/cleanup', $postDeployTasks)) {
array_unshift($postDeployTasks, 'deploy/targz/cleanup');
}
}
if ($this->runtime->getEnvironmentConfig('branch', false) && !$this->runtime->inRollback()) {
if (!in_array('git/change-branch', $postDeployTasks)) {
array_push($postDeployTasks, 'git/change-branch');
}
}
if (!$this->runTasks($output, $postDeployTasks)) {
throw new RuntimeException(sprintf('Stage "%s" did not finished successfully, halting command.', $this->getStageName()), 50);
}
}
/**
* Runs all the tasks
*
* @param OutputInterface $output
* @param $tasks
* @return bool
* @throws RuntimeException
*/
protected function runTasks(OutputInterface $output, $tasks)
{
if (count($tasks) == 0) {
$output->writeln(sprintf(' No tasks defined for <fg=black;options=bold>%s</> stage', $this->getStageName()));
$output->writeln('');
return true;
}
if ($this->runtime->getWorkingHost()) {
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks on host <fg=black;options=bold>%s</>:', $this->getStageName(), $this->runtime->getWorkingHost()));
} else {
$output->writeln(sprintf(' Starting <fg=black;options=bold>%s</> tasks:', $this->getStageName()));
}
$totalTasks = count($tasks);
$succeededTasks = 0;
foreach ($tasks as $taskName) {
/** @var AbstractTask $task */
$task = $this->taskFactory->get($taskName);
$output->write(sprintf(' Running <fg=magenta>%s</> ... ', $task->getDescription()));
$this->log(sprintf('Running task %s (%s)', $task->getDescription(), $task->getName()));
if ($this->runtime->inRollback() && !$task instanceof ExecuteOnRollbackInterface) {
$succeededTasks++;
$output->writeln('<fg=yellow>SKIPPED</>');
$this->log(sprintf('Task %s (%s) finished with SKIPPED, it was in a Rollback', $task->getDescription(), $task->getName()));
} else {
try {
if ($task->execute()) {
$succeededTasks++;
$output->writeln('<fg=green>OK</>');
$this->log(sprintf('Task %s (%s) finished with OK', $task->getDescription(), $task->getName()));
} else {
$output->writeln('<fg=red>FAIL</>');
$this->statusCode = 180;
$this->log(sprintf('Task %s (%s) finished with FAIL', $task->getDescription(), $task->getName()));
}
} catch (SkipException $exception) {
$succeededTasks++;
$output->writeln('<fg=yellow>SKIPPED</>');
$this->log(sprintf('Task %s (%s) finished with SKIPPED, thrown SkipException', $task->getDescription(), $task->getName()));
} catch (ErrorException $exception) {
$output->writeln(sprintf('<fg=red>ERROR</> [%s]', $exception->getTrimmedMessage()));
$this->log(sprintf('Task %s (%s) finished with FAIL, with Error "%s"', $task->getDescription(), $task->getName(), $exception->getMessage()));
$this->statusCode = 190;
}
}
if ($this->statusCode !== 0) {
break;
}
}
if ($succeededTasks != $totalTasks) {
$alertColor = 'red';
} else {
$alertColor = 'green';
}
$output->writeln(sprintf(' Finished <fg=%s>%d/%d</> tasks for <fg=black;options=bold>%s</>.', $alertColor, $succeededTasks, $totalTasks, $this->getStageName()));
$output->writeln('');
return ($succeededTasks == $totalTasks);
}
}
@@ -0,0 +1,147 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Releases;
use Mage\Utils;
use Mage\Runtime\Exception\RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* Command for Listing all Releases
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class ListCommand extends AbstractCommand
{
/**
* @var int
*/
protected $statusCode = 0;
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('releases:list')
->setDescription('List the releases on an environment')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$utils = new Utils();
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new RuntimeException('Releases are not enabled', 70);
}
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
$output->writeln('');
$hosts = $this->runtime->getEnvironmentConfig('hosts');
if (count($hosts) == 0) {
$output->writeln('No hosts defined');
$output->writeln('');
} else {
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
foreach ($hosts as $host) {
$this->runtime->setWorkingHost($host);
// Get List of Releases
$cmdListReleases = sprintf('ls -1 %s/releases', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdListReleases, false);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf('Unable to retrieve releases from host "%s"', $host), 80);
}
if (trim($process->getOutput()) != '') {
$releases = explode(PHP_EOL, trim($process->getOutput()));
rsort($releases);
} else {
$releases = [];
}
if (count($releases) == 0) {
$output->writeln(sprintf(' No releases available on host <fg=black;options=bold>%s</>:', $host));
} else {
// Get Current Release
$cmdCurrentRelease = sprintf('readlink -f %s/current', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdCurrentRelease, false);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf('Unable to retrieve current release from host "%s"', $host), 85);
}
$currentReleaseId = explode('/', trim($process->getOutput()));
$currentReleaseId = $currentReleaseId[count($currentReleaseId) - 1];
$output->writeln(sprintf(' Releases on host <fg=black;options=bold>%s</>:', $host));
foreach ($releases as $releaseId) {
$releaseDate = $utils->getReleaseDate($releaseId);
$output->write(sprintf(' Release ID: <fg=magenta>%s</> - Date: <fg=black;options=bold>%s</> [%s]',
$releaseId,
$releaseDate->format('Y-m-d H:i:s'),
$utils->getTimeDiff($releaseDate)
));
if ($releaseId == $currentReleaseId) {
$output->writeln(' <fg=red;options=bold>[current]</>');
} else {
$output->writeln('');
}
}
}
$this->runtime->setWorkingHost(null);
$output->writeln('');
}
}
} catch (RuntimeException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
$this->statusCode = $exception->getCode();
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return $this->statusCode;
}
}
@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn\Releases;
use Mage\Task\TaskFactory;
use Mage\Runtime\Exception\RuntimeException;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\BuiltIn\DeployCommand;
/**
* Command for Rolling Back a Releases
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class RollbackCommand extends DeployCommand
{
/**
* @var int
*/
protected $statusCode = 0;
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('releases:rollback')
->setDescription('Rollback to a release on an environment')
->addArgument('environment', InputArgument::REQUIRED, 'Name of the environment to deploy to')
->addArgument('release', InputArgument::REQUIRED, 'The ID or the Index of the release to rollback to')
;
}
/**
* Execute the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int|mixed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Starting <fg=blue>Magallanes</>');
$output->writeln('');
try {
$this->runtime->setEnvironment($input->getArgument('environment'));
if (!$this->runtime->getEnvironmentConfig('releases', false)) {
throw new RuntimeException('Releases are not enabled', 70);
}
$releaseToRollback = $input->getArgument('release');
if (($releaseId = $this->checkReleaseAvailability($releaseToRollback)) === false) {
throw new RuntimeException(sprintf('Release "%s" is not available on all hosts', $releaseToRollback), 72);
}
$this->runtime->setReleaseId($releaseId)->setRollback(true);
$output->writeln(sprintf(' Environment: <fg=green>%s</>', $this->runtime->getEnvironment()));
$this->log(sprintf('Environment: %s', $this->runtime->getEnvironment()));
$output->writeln(sprintf(' Rollback to Release Id: <fg=green>%s</>', $this->runtime->getReleaseId()));
$this->log(sprintf('Release ID: %s', $this->runtime->getReleaseId()));
if ($this->runtime->getConfigOptions('log_file', false)) {
$output->writeln(sprintf(' Logfile: <fg=green>%s</>', $this->runtime->getConfigOptions('log_file')));
}
$output->writeln('');
$this->taskFactory = new TaskFactory($this->runtime);
$this->runDeployment($output);
} catch (RuntimeException $exception) {
$output->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
$this->statusCode = $exception->getCode();
}
$output->writeln('Finished <fg=blue>Magallanes</>');
return $this->statusCode;
}
/**
* Check if the provided Release ID is available in all hosts
*
* @param string $releaseToRollback Release ID
* @return bool
*/
protected function checkReleaseAvailability($releaseToRollback)
{
$hosts = $this->runtime->getEnvironmentConfig('hosts');
$hostPath = rtrim($this->runtime->getEnvironmentConfig('host_path'), '/');
$availableInHosts = 0;
foreach ($hosts as $host) {
$releases = [];
$this->runtime->setWorkingHost($host);
// Get List of Releases
$cmdListReleases = sprintf('ls -1 %s/releases', $hostPath);
/** @var Process $process */
$process = $this->runtime->runRemoteCommand($cmdListReleases, false);
if ($process->isSuccessful()) {
$releases = explode(PHP_EOL, trim($process->getOutput()));
rsort($releases);
}
if (in_array($releaseToRollback, $releases)) {
$availableInHosts++;
}
$this->runtime->setWorkingHost(null);
}
if ($availableInHosts === count($hosts)) {
return $releaseToRollback;
}
return false;
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Magallanes package.
*
* (c) Andrés Montañez <andres@andresmontanez.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mage\Command\BuiltIn;
use Mage\Mage;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Mage\Command\AbstractCommand;
/**
* Version Command, return the current version of Magallanes
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
class VersionCommand extends AbstractCommand
{
/**
* Configure the Command
*/
protected function configure()
{
$this
->setName('version')
->setDescription('Get the version of Magallanes')
;
}
/**
* Executes the Command
*
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln(sprintf('Magallanes v%s [%s]', Mage::VERSION, Mage::CODENAME));
return 0;
}
}