From 4c3f50608e3936df15f6d18fe7946f598ac469f0 Mon Sep 17 00:00:00 2001 From: Mario Mueller Date: Fri, 2 May 2014 16:43:31 +0200 Subject: [PATCH] The git remote cache deployment task. This tasks uses a remote checkout on the server to provide the release. In our use case this remote cache resides in $to/$shared/git-remote-cache, variable 'shared' is substituted with "shared" by default. At this time, the remote cache is not build automatically, you need to provide a clean checkout before you can start using it. --- Mage/Command/BuiltIn/DeployCommand.php | 420 +++++++++--------- .../Strategy/GitRemoteCacheTask.php | 137 ++++++ 2 files changed, 349 insertions(+), 208 deletions(-) create mode 100644 Mage/Task/BuiltIn/Deployment/Strategy/GitRemoteCacheTask.php diff --git a/Mage/Command/BuiltIn/DeployCommand.php b/Mage/Command/BuiltIn/DeployCommand.php index 916d546..3140f2f 100644 --- a/Mage/Command/BuiltIn/DeployCommand.php +++ b/Mage/Command/BuiltIn/DeployCommand.php @@ -30,28 +30,28 @@ use Exception; */ class DeployCommand extends AbstractCommand implements RequiresEnvironment { - /** - * Deploy has Failed - * @var string - */ - const FAILED = 'failed'; + /** + * Deploy has Failed + * @var string + */ + const FAILED = 'failed'; - /** - * Deploy has Succeded - * @var string - */ - const SUCCEDED = 'succeded'; + /** + * Deploy has Succeded + * @var string + */ + const SUCCEDED = 'succeded'; - /** - * Deploy is in progress - * @var string - */ - const IN_PROGRESS = 'in_progress'; + /** + * Deploy is in progress + * @var string + */ + const IN_PROGRESS = 'in_progress'; - /** - * Time the Deployment has Started - * @var integer - */ + /** + * Time the Deployment has Started + * @var integer + */ protected $startTime = null; /** @@ -91,7 +91,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment */ public static function getStatus() { - return self::$deployStatus; + return self::$deployStatus; } /** @@ -101,19 +101,19 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment public function run() { // Check if Environment is not Locked - $lockFile = '.mage/' . $this->getConfig()->getEnvironment() . '.lock'; - if (file_exists($lockFile)) { - Console::output('This environment is locked!', 1, 2); - return; - } + $lockFile = '.mage/' . $this->getConfig()->getEnvironment() . '.lock'; + if (file_exists($lockFile)) { + Console::output('This environment is locked!', 1, 2); + return; + } - // Check for running instance and Lock - if (file_exists('.mage/~working.lock')) { - Console::output('There is already an instance of Magallanes running!', 1, 2); - return; - } else { - touch('.mage/~working.lock'); - } + // Check for running instance and Lock + if (file_exists('.mage/~working.lock')) { + Console::output('There is already an instance of Magallanes running!', 1, 2); + return; + } else { + touch('.mage/~working.lock'); + } // Release ID $this->getConfig()->setReleaseId(date('YmdHis')); @@ -126,15 +126,15 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment // Deploy Summary - Releases if ($this->getConfig()->release('enabled', false)) { - Console::output('Release ID: ' . $this->getConfig()->getReleaseId() . '', 2, 1); + Console::output('Release ID: ' . $this->getConfig()->getReleaseId() . '', 2, 1); } // Deploy Summary - SCM if ($this->getConfig()->deployment('scm', false)) { - $scmConfig = $this->getConfig()->deployment('scm'); - if (isset($scmConfig['branch'])) { - Console::output('SCM Branch: ' . $scmConfig['branch'] . '', 2, 1); - } + $scmConfig = $this->getConfig()->deployment('scm'); + if (isset($scmConfig['branch'])) { + Console::output('SCM Branch: ' . $scmConfig['branch'] . '', 2, 1); + } } // Deploy Summary - Separator Line @@ -147,21 +147,21 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment // Check Status if (self::$failedTasks > 0) { - self::$deployStatus = self::FAILED; - Console::output('A total of ' . self::$failedTasks . ' deployment tasks failed: ABORTING', 1, 2); + self::$deployStatus = self::FAILED; + Console::output('A total of ' . self::$failedTasks . ' deployment tasks failed: ABORTING', 1, 2); } else { - // Run Deployment Tasks - $this->runDeploymentTasks(); + // Run Deployment Tasks + $this->runDeploymentTasks(); - // Check Status - if (self::$failedTasks > 0) { - self::$deployStatus = self::FAILED; - Console::output('A total of ' . self::$failedTasks . ' deployment tasks failed: ABORTING', 1, 2); - } + // Check Status + if (self::$failedTasks > 0) { + self::$deployStatus = self::FAILED; + Console::output('A total of ' . self::$failedTasks . ' deployment tasks failed: ABORTING', 1, 2); + } - // Run Post-Deployment Tasks - $this->runNonDeploymentTasks(AbstractTask::STAGE_POST_DEPLOY, $this->getConfig(), 'Post-Deployment'); + // Run Post-Deployment Tasks + $this->runNonDeploymentTasks(AbstractTask::STAGE_POST_DEPLOY, $this->getConfig(), 'Post-Deployment'); } // Time Information Hosts @@ -182,7 +182,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment // Unlock if (file_exists('.mage/~working.lock')) { - unlink('.mage/~working.lock'); + unlink('.mage/~working.lock'); } } @@ -200,33 +200,33 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment // PreDeployment Hook if ($stage == AbstractTask::STAGE_PRE_DEPLOY) { - // Look for Remote Source - if (is_array($config->deployment('source', null))) { - array_unshift($tasksToRun, 'scm/clone'); - } + // Look for Remote Source + if (is_array($config->deployment('source', null))) { + array_unshift($tasksToRun, 'scm/clone'); + } - // Change Branch - if ($config->deployment('scm', false)) { - array_unshift($tasksToRun, 'scm/change-branch'); - } + // Change Branch + if ($config->deployment('scm', false)) { + array_unshift($tasksToRun, 'scm/change-branch'); + } } // PostDeployment Hook if ($stage == AbstractTask::STAGE_POST_DEPLOY) { - // If Deploy failed, clear post deploy tasks - if (self::$deployStatus == self::FAILED) { - $tasksToRun = array(); - } + // If Deploy failed, clear post deploy tasks + if (self::$deployStatus == self::FAILED) { + $tasksToRun = array(); + } - // Change Branch Back - if ($config->deployment('scm', false)) { - array_unshift($tasksToRun, 'scm/change-branch'); - $config->addParameter('_changeBranchRevert'); - } + // Change Branch Back + if ($config->deployment('scm', false)) { + array_unshift($tasksToRun, 'scm/change-branch'); + $config->addParameter('_changeBranchRevert'); + } - // Remove Remote Source - if (is_array($config->deployment('source', null))) { - array_push($tasksToRun, 'scm/remove-clone'); + // Remove Remote Source + if (is_array($config->deployment('source', null))) { + array_push($tasksToRun, 'scm/remove-clone'); } } @@ -246,7 +246,7 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment if ($this->runTask($task)) { $completedTasks++; } else { - self::$failedTasks++; + self::$failedTasks++; } } @@ -262,173 +262,177 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment protected function runDeploymentTasks() { - if (self::$deployStatus == self::FAILED) { - return; - } + if (self::$deployStatus == self::FAILED) { + return; + } - // Run Tasks for Deployment - $hosts = $this->getConfig()->getHosts(); - $this->hostsCount = count($hosts); - self::$failedTasks = 0; + // Run Tasks for Deployment + $hosts = $this->getConfig()->getHosts(); + $this->hostsCount = count($hosts); + self::$failedTasks = 0; - if ($this->hostsCount == 0) { - Console::output('Warning! No hosts defined, skipping deployment tasks.', 1, 3); + if ($this->hostsCount == 0) { + Console::output('Warning! No hosts defined, skipping deployment tasks.', 1, 3); - } else { - $this->startTimeHosts = time(); - foreach ($hosts as $hostKey => $host) { + } else { + $this->startTimeHosts = time(); + foreach ($hosts as $hostKey => $host) { - // Check if Host has specific configuration - $hostConfig = null; - if (is_array($host)) { - $hostConfig = $host; - $host = $hostKey; - } + // Check if Host has specific configuration + $hostConfig = null; + if (is_array($host)) { + $hostConfig = $host; + $host = $hostKey; + } - // Set Host and Host Specific Config - $this->getConfig()->setHost($host); - $this->getConfig()->setHostConfig($hostConfig); + // Set Host and Host Specific Config + $this->getConfig()->setHost($host); + $this->getConfig()->setHostConfig($hostConfig); - // Prepare Tasks - $tasks = 0; - $completedTasks = 0; + // Prepare Tasks + $tasks = 0; + $completedTasks = 0; - Console::output('Deploying to ' . $this->getConfig()->getHost() . ''); + Console::output('Deploying to ' . $this->getConfig()->getHost() . ''); - $tasksToRun = $this->getConfig()->getTasks(); + $tasksToRun = $this->getConfig()->getTasks(); - // Guess a Deploy Strategy - switch ($this->getConfig()->deployment('strategy', 'guess')) { - case 'disabled': - $deployStrategy = 'deployment/strategy/disabled'; - break; + // Guess a Deploy Strategy + switch ($this->getConfig()->deployment('strategy', 'guess')) { + case 'disabled': + $deployStrategy = 'deployment/strategy/disabled'; + break; - case 'rsync': - $deployStrategy = 'deployment/strategy/rsync'; - break; + case 'rsync': + $deployStrategy = 'deployment/strategy/rsync'; + break; - case 'targz': - $deployStrategy = 'deployment/strategy/tar-gz'; - break; + case 'targz': + $deployStrategy = 'deployment/strategy/tar-gz'; + break; - case 'guess': - default: - if ($this->getConfig()->release('enabled', false) == true) { - $deployStrategy = 'deployment/strategy/tar-gz'; - } else { - $deployStrategy = 'deployment/strategy/rsync'; - } - break; - } + case 'remote-cache': + $deployStrategy = 'deployment/strategy/git-remote-cache'; + break; - array_unshift($tasksToRun, $deployStrategy); + case 'guess': + default: + if ($this->getConfig()->release('enabled', false) == true) { + $deployStrategy = 'deployment/strategy/tar-gz'; + } else { + $deployStrategy = 'deployment/strategy/rsync'; + } + break; + } - if (count($tasksToRun) == 0) { - Console::output('Warning! No Deployment tasks defined.', 2); - Console::output('Deployment to ' . $host . ' skipped!', 1, 3); + array_unshift($tasksToRun, $deployStrategy); - } else { - foreach ($tasksToRun as $taskData) { - $tasks++; - $task = Factory::get($taskData, $this->getConfig(), false, AbstractTask::STAGE_DEPLOY); + if (count($tasksToRun) == 0) { + Console::output('Warning! No Deployment tasks defined.', 2); + Console::output('Deployment to ' . $host . ' skipped!', 1, 3); - if ($this->runTask($task)) { - $completedTasks++; - } else { - self::$failedTasks++; - } - } + } else { + foreach ($tasksToRun as $taskData) { + $tasks++; + $task = Factory::get($taskData, $this->getConfig(), false, AbstractTask::STAGE_DEPLOY); - if ($completedTasks == $tasks) { - $tasksColor = 'green'; - } else { - $tasksColor = 'red'; - } + if ($this->runTask($task)) { + $completedTasks++; + } else { + self::$failedTasks++; + } + } - Console::output('Deployment to ' . $this->getConfig()->getHost() . ' completed: <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); - } + if ($completedTasks == $tasks) { + $tasksColor = 'green'; + } else { + $tasksColor = 'red'; + } - // Reset Host Config - $this->getConfig()->setHostConfig(null); - } - $this->endTimeHosts = time(); + Console::output('Deployment to ' . $this->getConfig()->getHost() . ' completed: <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); + } - if (self::$failedTasks > 0) { - self::$deployStatus = self::FAILED; - } else { - self::$deployStatus = self::SUCCEDED; - } + // Reset Host Config + $this->getConfig()->setHostConfig(null); + } + $this->endTimeHosts = time(); - // Releasing - if (self::$deployStatus == self::SUCCEDED && $this->getConfig()->release('enabled', false) == true) { - // Execute the Releases - Console::output('Starting the Releasing'); - foreach ($hosts as $hostKey => $host) { + if (self::$failedTasks > 0) { + self::$deployStatus = self::FAILED; + } else { + self::$deployStatus = self::SUCCEDED; + } - // Check if Host has specific configuration - $hostConfig = null; - if (is_array($host)) { - $hostConfig = $host; - $host = $hostKey; - } + // Releasing + if (self::$deployStatus == self::SUCCEDED && $this->getConfig()->release('enabled', false) == true) { + // Execute the Releases + Console::output('Starting the Releasing'); + foreach ($hosts as $hostKey => $host) { - // Set Host - $this->getConfig()->setHost($host); - $this->getConfig()->setHostConfig($hostConfig); + // Check if Host has specific configuration + $hostConfig = null; + if (is_array($host)) { + $hostConfig = $host; + $host = $hostKey; + } - $task = Factory::get('deployment/release', $this->getConfig(), false, AbstractTask::STAGE_DEPLOY); + // Set Host + $this->getConfig()->setHost($host); + $this->getConfig()->setHostConfig($hostConfig); - if ($this->runTask($task, 'Releasing on host ' . $host . ' ... ')) { - $completedTasks++; - } + $task = Factory::get('deployment/release', $this->getConfig(), false, AbstractTask::STAGE_DEPLOY); - // Reset Host Config - $this->getConfig()->setHostConfig(null); - } - Console::output('Finished the Releasing', 1, 3); + if ($this->runTask($task, 'Releasing on host ' . $host . ' ... ')) { + $completedTasks++; + } - // Execute the Post-Release Tasks - foreach ($hosts as $hostKey => $host) { + // Reset Host Config + $this->getConfig()->setHostConfig(null); + } + Console::output('Finished the Releasing', 1, 3); - // Check if Host has specific configuration - $hostConfig = null; - if (is_array($host)) { - $hostConfig = $host; - $host = $hostKey; - } + // Execute the Post-Release Tasks + foreach ($hosts as $hostKey => $host) { - // Set Host - $this->getConfig()->setHost($host); - $this->getConfig()->setHostConfig($hostConfig); + // Check if Host has specific configuration + $hostConfig = null; + if (is_array($host)) { + $hostConfig = $host; + $host = $hostKey; + } - $tasksToRun = $this->getConfig()->getTasks(AbstractTask::STAGE_POST_RELEASE); - $tasks = count($tasksToRun); - $completedTasks = 0; + // Set Host + $this->getConfig()->setHost($host); + $this->getConfig()->setHostConfig($hostConfig); - if (count($tasksToRun) > 0) { - Console::output('Starting Post-Release tasks for ' . $host . ':'); + $tasksToRun = $this->getConfig()->getTasks(AbstractTask::STAGE_POST_RELEASE); + $tasks = count($tasksToRun); + $completedTasks = 0; - foreach ($tasksToRun as $task) { - $task = Factory::get($task, $this->getConfig(), false, AbstractTask::STAGE_POST_RELEASE); + if (count($tasksToRun) > 0) { + Console::output('Starting Post-Release tasks for ' . $host . ':'); - if ($this->runTask($task)) { - $completedTasks++; - } - } + foreach ($tasksToRun as $task) { + $task = Factory::get($task, $this->getConfig(), false, AbstractTask::STAGE_POST_RELEASE); - if ($completedTasks == $tasks) { - $tasksColor = 'green'; - } else { - $tasksColor = 'red'; - } - Console::output('Finished Post-Release tasks for ' . $host . ': <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); - } + if ($this->runTask($task)) { + $completedTasks++; + } + } - // Reset Host Config - $this->getConfig()->setHostConfig(null); - } - } - } + if ($completedTasks == $tasks) { + $tasksColor = 'green'; + } else { + $tasksColor = 'red'; + } + Console::output('Finished Post-Release tasks for ' . $host . ': <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); + } + + // Reset Host Config + $this->getConfig()->setHostConfig(null); + } + } + } } /** @@ -466,8 +470,8 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment $result = false; } } catch (ErrorWithMessageException $e) { - Console::output('FAIL [Message: ' . $e->getMessage() . ']', 0); - $result = false; + Console::output('FAIL [Message: ' . $e->getMessage() . ']', 0); + $result = false; } catch (SkipException $e) { Console::output('SKIPPED', 0); @@ -519,11 +523,11 @@ class DeployCommand extends AbstractCommand implements RequiresEnvironment */ protected function sendNotification($result) { - $projectName = $this->getConfig()->general('name', false); - $projectEmail = $this->getConfig()->general('email', false); - $notificationsEnabled = $this->getConfig()->general('notifications', false); + $projectName = $this->getConfig()->general('name', false); + $projectEmail = $this->getConfig()->general('email', false); + $notificationsEnabled = $this->getConfig()->general('notifications', false); - // We need notifications enabled, and a project name and email to send the notification + // We need notifications enabled, and a project name and email to send the notification if (!$projectName || !$projectEmail || !$notificationsEnabled) { return false; } diff --git a/Mage/Task/BuiltIn/Deployment/Strategy/GitRemoteCacheTask.php b/Mage/Task/BuiltIn/Deployment/Strategy/GitRemoteCacheTask.php new file mode 100644 index 0000000..43296c3 --- /dev/null +++ b/Mage/Task/BuiltIn/Deployment/Strategy/GitRemoteCacheTask.php @@ -0,0 +1,137 @@ + + */ +class GitRemoteCacheTask extends AbstractTask implements IsReleaseAware +{ + /** + * Returns the Title of the Task + * @return string + */ + public function getName() + { + return 'Deploy via remote cached git repository [built-in]'; + } + + /** + * Runs the task + * + * @return boolean + * @throws Exception + * @throws ErrorWithMessageException + * @throws SkipException + */ + public function run() + { + $overrideRelease = $this->getParameter('overrideRelease', false); + + if ($overrideRelease == true) { + $releaseToOverride = false; + $resultFetch = $this->runCommandRemote('ls -ld current | cut -d"/" -f2', $releaseToOverride); + if ($resultFetch && is_numeric($releaseToOverride)) { + $this->getConfig()->setReleaseId($releaseToOverride); + } + } + + $excludes = array( + '.git', + '.svn', + '.mage', + '.gitignore', + '.gitkeep', + 'nohup.out' + ); + + // Look for User Excludes + $userExcludes = $this->getConfig()->deployment('excludes', array()); + + $deployToDirectory = $this->getConfig()->deployment('to'); + if ($this->getConfig()->release('enabled', false) == true) { + $releasesDirectory = $this->getConfig()->release('directory', 'releases'); + + $deployToDirectory = rtrim($this->getConfig()->deployment('to'), '/') + . '/' . $releasesDirectory + . '/' . $this->getConfig()->getReleaseId(); + $this->runCommandRemote('mkdir -p ' . $releasesDirectory . '/' . $this->getConfig()->getReleaseId()); + } + + $branch = $this->getParameter('branch'); + $remote = $this->getParameter('remote', 'origin'); + + $remoteCacheParam = $this->getParameter('remote_cache', 'shared/git-remote-cache'); + $remoteCacheFolder = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $remoteCacheParam; + + // Don't use -C as git 1.7 does not support it + $command = 'cd ' . $remoteCacheFolder . ' && /usr/bin/env git fetch ' . $remote; + $result = $this->runCommandRemote($command); + + $command = 'cd ' . $remoteCacheFolder . ' && /usr/bin/env git checkout ' . $branch; + $result = $this->runCommandRemote($command) && $result; + + $command = 'cd ' . $remoteCacheFolder . ' && /usr/bin/env git pull --rebase ' . $branch; + $result = $this->runCommandRemote($command) && $result; + + $excludes = array_merge($excludes, $userExcludes); + $excludeCmd = ''; + foreach ($excludes as $excludeFile) { + $excludeCmd .= ' --exclude=' . $excludeFile; + } + + $command = 'cd ' . $remoteCacheFolder . ' && /usr/bin/env git archive ' . $branch . ' | tar -x -C ' . $deployToDirectory . ' ' . $excludeCmd; + $result = $this->runCommandRemote($command) && $result; + + // Count Releases + if ($this->getConfig()->release('enabled', false) == true) { + $releasesDirectory = $this->getConfig()->release('directory', 'releases'); + $symlink = $this->getConfig()->release('symlink', 'current'); + + if (substr($symlink, 0, 1) == '/') { + $releasesDirectory = rtrim($this->getConfig()->deployment('to'), '/') . '/' . $releasesDirectory; + } + + $maxReleases = $this->getConfig()->release('max', false); + if (($maxReleases !== false) && ($maxReleases > 0)) { + $releasesList = ''; + $countReleasesFetch = $this->runCommandRemote('ls -1 ' . $releasesDirectory, $releasesList); + $releasesList = trim($releasesList); + + if ($countReleasesFetch && $releasesList != '') { + $releasesList = explode(PHP_EOL, $releasesList); + if (count($releasesList) > $maxReleases) { + $releasesToDelete = array_diff($releasesList, array($this->getConfig()->getReleaseId())); + sort($releasesToDelete); + $releasesToDeleteCount = count($releasesToDelete) - $maxReleases; + $releasesToDelete = array_slice($releasesToDelete, 0, $releasesToDeleteCount + 1); + + foreach ($releasesToDelete as $releaseIdToDelete) { + $directoryToDelete = $releasesDirectory . '/' . $releaseIdToDelete; + if ($directoryToDelete != '/') { + $command = 'rm -rf ' . $directoryToDelete; + $result = $result && $this->runCommandRemote($command); + } + } + } + } + } + } + + return $result; + } +}