diff --git a/Mage/Command/BuiltIn/DeployCommand.php b/Mage/Command/BuiltIn/DeployCommand.php index 8dd6c9d..193e792 100644 --- a/Mage/Command/BuiltIn/DeployCommand.php +++ b/Mage/Command/BuiltIn/DeployCommand.php @@ -129,6 +129,24 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment return self::$deployStatus; } + /** + * Launch a Dry run command for Rsync strategy + * Without others sub tasks. + */ + public function dryRun() + { + if ($this->getConfig()->deployment('strategy') != 'rsync') { + Console::output('Error : Dry-run task is only available when using Rsync strategy.', 1, 1); + return 232; + } + else { + // Launch Pre-DryRun tasks. + $this->runNonDeploymentTasks(AbstractTask::STAGE_PRE_DRYRUN, $this->getConfig(), 'Pre-DryRun'); + // Launch Deployment in dryrun mode (true). + $this->runDeploymentTasks(TRUE); + } + } + /** * Deploys the Application * @see \Mage\Command\AbstractCommand::run() @@ -153,6 +171,11 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment touch(getcwd() . '/.mage/~working.lock'); } + // If we are in dry run only + if ($this->getConfig()->getParameter('dry-run') === true) { + return $this->dryRun(); + } + // Release ID $this->getConfig()->setReleaseId(date('YmdHis')); @@ -229,7 +252,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment if (self::$deployStatus === self::FAILED) { $exitCode = 1; } - + return $exitCode; } @@ -306,7 +329,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment } } - protected function runDeploymentTasks() + protected function runDeploymentTasks($dryrun = false) { if (self::$deployStatus == self::FAILED) { return; @@ -340,7 +363,11 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment Console::output('Deploying to ' . $this->getConfig()->getHost() . ''); - $tasksToRun = $this->getConfig()->getTasks(); + $tasksToRun = array(); + + if ($dryrun !== false) { + $tasksToRun = $this->getConfig()->getTasks(); + } $deployStrategy = $this->chooseDeployStrategy(); @@ -382,7 +409,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment } // Releasing - if (self::$deployStatus == self::SUCCEDED && $this->getConfig()->release('enabled', false) === true) { + if (self::$deployStatus == self::SUCCEDED && $dryrun !== true && $this->getConfig()->release('enabled', false) === true) { // Execute the Releases Console::output('Starting the Releasing'); $completedTasks = 0; @@ -522,7 +549,6 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment if ($runTask === true) { try { $result = $task->run(); - if ($result === true) { Console::output('OK', 0); $result = true; diff --git a/Mage/Task/AbstractTask.php b/Mage/Task/AbstractTask.php index ec1d4e0..cce9c6f 100644 --- a/Mage/Task/AbstractTask.php +++ b/Mage/Task/AbstractTask.php @@ -28,6 +28,12 @@ abstract class AbstractTask */ const STAGE_PRE_DEPLOY = 'pre-deploy'; + /** + * Stage Constant for Pre Dry-Run + * @var string + */ + const STAGE_PRE_DRYRUN = 'pre-dryrun'; + /** * Stage Constant for Deployment * @var string diff --git a/Mage/Task/BuiltIn/Deployment/Strategy/RsyncTask.php b/Mage/Task/BuiltIn/Deployment/Strategy/RsyncTask.php index 16804b0..2e180c7 100644 --- a/Mage/Task/BuiltIn/Deployment/Strategy/RsyncTask.php +++ b/Mage/Task/BuiltIn/Deployment/Strategy/RsyncTask.php @@ -13,6 +13,7 @@ namespace Mage\Task\BuiltIn\Deployment\Strategy; use Mage\Console; use Mage\Task\BuiltIn\Deployment\Strategy\BaseStrategyTaskAbstract; use Mage\Task\Releases\IsReleaseAware; +use Mage\Task\BuiltIn\Releases\ListTask; /** * Task for Sync the Local Code to the Remote Hosts via RSYNC @@ -27,6 +28,9 @@ class RsyncTask extends BaseStrategyTaskAbstract implements IsReleaseAware */ public function getName() { + if ($this->getConfig()->getParameter('dry-run', false) === true) { + return 'Dry-run via Rsync [built-in]'; + } if ($this->getConfig()->release('enabled', false) === true) { if ($this->getConfig()->getParameter('overrideRelease', false) === true) { return 'Deploy via Rsync (with Releases override) [built-in]'; @@ -43,27 +47,130 @@ class RsyncTask extends BaseStrategyTaskAbstract implements IsReleaseAware } } + + /** + * @param bool|FALSE $dryRun + * @return string + */ + protected function getDeployCommand($dryRun = false) + { + $deployToDirectory = $this->getDeployToDirectory($dryRun); + + $excludes = $this->getExcludes(); + $excludesListFilePath = $this->getConfig()->deployment('excludes_file', ''); + + $strategyFlags = $this->getConfig()->deployment('strategy_flags', $this->getConfig()->general('strategy_flags', array())); + if (isset($strategyFlags['rsync'])) { + $strategyFlags = $strategyFlags['rsync']; + } else { + $strategyFlags = ''; + } + + // Add two flags if we are in Dry run. + if ($dryRun === true) { + $dryRunFlags = array( + '--dry-run', + '--itemize-changes', + '--omit-dir-times' + ); + $strategyFlags = str_replace($dryRunFlags, '', $strategyFlags); + $strategyFlags .= implode(' ', $dryRunFlags); + } + + $command = 'rsync -avz ' + . $strategyFlags . ' ' + . '--rsh="ssh ' . $this->getConfig()->getHostIdentityFileOption() . '-p' . $this->getConfig()->getHostPort() . '" ' + . $this->excludes($excludes) . ' ' + . $this->excludesListFile($excludesListFilePath) . ' ' + . $this->getConfig()->deployment('from') . ' ' + . ($this->getConfig()->deployment('user') ? $this->getConfig()->deployment('user') . '@' : '') + . $this->getConfig()->getHostName() . ':' . $deployToDirectory; + + return $command; + } + + /** + * @param bool|FALSE $dryRun + * @return string + */ + protected function getDeployToDirectory($dryRun = false) + { + $deployTo = rtrim($this->getConfig()->deployment('to'), '/'); + + // If releases aren't enabled, deploy dir is simply deploy to config. + if ($this->getConfig()->release('enabled', false) !== true) { + return $deployTo; + } + + // If dryrun, check from the latest release. + if ($dryRun === true) { + $release = ListTask::getCurrentRelease(); + } + else { + $release = $this->getConfig()->getReleaseId(); + } + $releasesDirectory = $this->getConfig()->release('directory', 'releases'); + $deployToDirectory = $deployTo . '/' . $releasesDirectory . '/' . $release; + + return $deployToDirectory; + } + + /** + * @return bool + */ + public function dryRun() + { + Console::output(''); + if ($this->getConfig()->release('enabled', false) === true) { + $currentRelease = ListTask::getCurrentRelease(); + Console::output('Checking files on the latest release '.$currentRelease.' on host '.$this->getConfig()->getHostName().'...', 2, 1); + } + else { + Console::output('Checking files on host '.$this->getConfig()->getHostName().'...', 2, 1); + } + + // Get deploy command with dryRun option (true). + $command = $this->getDeployCommand(true); + $result = $this->runCommandLocal($command, $output); + + // Put each line in an array (CHR(10) = Carriage return). + $lines = explode(CHR(10), $output); + if (count($lines)) { + Console::output(''); + Console::output('---------- Dry run result ------------', 2, 1); + foreach ($lines as $key => $line) { + Console::output($line, 2, 1); + } + Console::output('---------- End Dry run ---------------', 2, 1); + } + return $result; + + } + /** * Syncs the Local Code to the Remote Host * @see \Mage\Task\AbstractTask::run() */ public function run() { + $excludes = $this->getExcludes(); + + // Launch dry run mode + if ($this->getConfig()->getParameter('dry-run') === true) { + return $this->dryRun(); + } + $this->checkOverrideRelease(); - $excludes = $this->getExcludes(); - $excludesListFilePath = $this->getConfig()->deployment('excludes_file', ''); - // If we are working with releases - $deployToDirectory = $this->getConfig()->deployment('to'); if ($this->getConfig()->release('enabled', false) === true) { + $releasesDirectory = $this->getConfig()->release('directory', 'releases'); $symlink = $this->getConfig()->release('symlink', 'current'); $currentRelease = false; - $deployToDirectory = rtrim($this->getConfig()->deployment('to'), '/') - . '/' . $releasesDirectory - . '/' . $this->getConfig()->getReleaseId(); + + $deployToDirectory = $this->getDeployToDirectory(); Console::log('Deploy to ' . $deployToDirectory); $resultFetch = $this->runCommandRemote('ls -ld ' . $symlink . ' | cut -d"/" -f2', $currentRelease); @@ -86,23 +193,7 @@ class RsyncTask extends BaseStrategyTaskAbstract implements IsReleaseAware } } - // Strategy Flags - $strategyFlags = $this->getConfig()->deployment('strategy_flags', $this->getConfig()->general('strategy_flags', array())); - if (isset($strategyFlags['rsync'])) { - $strategyFlags = $strategyFlags['rsync']; - } else { - $strategyFlags = ''; - } - - $command = 'rsync -avz ' - . $strategyFlags . ' ' - . '--rsh="ssh ' . $this->getConfig()->getHostIdentityFileOption() . '-p' . $this->getConfig()->getHostPort() . '" ' - . $this->excludes($excludes) . ' ' - . $this->excludesListFile($excludesListFilePath) . ' ' - . $this->getConfig()->deployment('from') . ' ' - . ($this->getConfig()->deployment('user') ? $this->getConfig()->deployment('user') . '@' : '') - . $this->getConfig()->getHostName() . ':' . $deployToDirectory; - + $command = $this->getDeployCommand(); $result = $this->runCommandLocal($command); return $result; diff --git a/Mage/Task/BuiltIn/Releases/ListTask.php b/Mage/Task/BuiltIn/Releases/ListTask.php index 9b86852..05426f9 100644 --- a/Mage/Task/BuiltIn/Releases/ListTask.php +++ b/Mage/Task/BuiltIn/Releases/ListTask.php @@ -45,9 +45,7 @@ class ListTask extends AbstractTask implements IsReleaseAware $releases = ($output == '') ? array() : explode(PHP_EOL, $output); // Get Current - $result = $this->runCommandRemote('ls -l ' . $symlink, $output) && $result; - $currentRelease = explode('/', $output); - $currentRelease = trim(array_pop($currentRelease)); + $currentRelease = $this->getCurrentRelease(); if (count($releases) == 0) { Console::output('No releases available ... ', 2); @@ -92,6 +90,18 @@ class ListTask extends AbstractTask implements IsReleaseAware } } + /** + * Get the latest release. + */ + public function getCurrentRelease() + { + $symlink = $this->getConfig()->release('symlink', 'current'); + $this->runCommandRemote('ls -l ' . $symlink, $output); + $currentRelease = explode('/', $output); + $currentRelease = trim(array_pop($currentRelease)); + return $currentRelease; + } + /** * Calculates a Human Readable Time Difference * @param string $releaseDate diff --git a/docs/commands.txt b/docs/commands.txt index 178bad2..424ede9 100644 --- a/docs/commands.txt +++ b/docs/commands.txt @@ -30,6 +30,9 @@ mage deploy to:production # Deploys Application to Production environment, overriding the current release mage deploy to:production --overrideRelease +# Don't deploy application, only output the files to be deployed (works with Rsync strategy only). +mage deploy to:production --dry-run + # Locks deployment to Production environment mage lock to:production diff --git a/docs/example-config/.mage/config/environment/production.yml b/docs/example-config/.mage/config/environment/production.yml index 1af997f..96791e1 100644 --- a/docs/example-config/.mage/config/environment/production.yml +++ b/docs/example-config/.mage/config/environment/production.yml @@ -14,6 +14,8 @@ hosts: - s01.example.com - s02.example.com tasks: + pre-dryrun: +# - grunt pre-deploy: - scm/update on-deploy: diff --git a/docs/example-config/.mage/config/environment/staging.yml b/docs/example-config/.mage/config/environment/staging.yml index 51f4f06..e5e9a55 100644 --- a/docs/example-config/.mage/config/environment/staging.yml +++ b/docs/example-config/.mage/config/environment/staging.yml @@ -14,6 +14,8 @@ hosts: - localhost - 127.0.0.1 tasks: + pre-dryrun: +# - grunt pre-deploy: # - sampleTask # - failTask