* * 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\Command\AbstractCommand; use Mage\Command\RequiresEnvironment; use Mage\Task\Factory; use Mage\Task\Releases\SkipOnOverride; use Mage\Task\ErrorWithMessageException; use Mage\Task\SkipException; use Mage\Console; use Mage\Config; use Exception; /** * Command for Deploying * * @author Andrés Montañez */ class DeployCommand extends AbstractCommand implements RequiresEnvironment { /** * Deploy has Failed * @var string */ const FAILED = 'failed'; /** * Deploy has Succeded * @var string */ const SUCCEDED = 'succeded'; /** * Deploy is in progress * @var string */ const IN_PROGRESS = 'in_progress'; /** * Time the Deployment has Started * @var integer */ protected $startTime = null; /** * Time the Deployment has Started to the current Host * @var integer */ protected $startTimeHosts = null; /** * Time the Deployment to the Hosts has Finished * @var integer */ protected $endTimeHosts = null; /** * Quantity of Hosts to Deploy to. * @var integer */ protected $hostsCount = 0; /** * Current Status of the Deployment (in progress, succeded, failed) * @var string */ protected static $deployStatus = 'in_progress'; /** * Total of Failed tasks * @var integer */ protected static $failedTasks = 0; /** * Returns the Status of the Deployment * * @return string */ public static function getStatus() { return self::$deployStatus; } /** * Deploys the Application * @see \Mage\Command\AbstractCommand::run() */ 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; } // 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')); // Deploy Summary Console::output('Deploy summary', 1, 1); // Deploy Summary - Environment Console::output('Environment: ' . $this->getConfig()->getEnvironment() . '', 2, 1); // Deploy Summary - Releases if ($this->getConfig()->release('enabled', false)) { 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); } } // Deploy Summary - Separator Line Console::output('', 0, 1); $this->startTime = time(); // Run Pre-Deployment Tasks $this->runNonDeploymentTasks('pre-deploy', $this->getConfig(), 'Pre-Deployment'); // Check Status if (self::$failedTasks > 0) { self::$deployStatus = self::FAILED; Console::output('A total of ' . self::$failedTasks . ' deployment tasks failed: ABORTING', 1, 2); } else { // 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); } // Run Post-Deployment Tasks $this->runNonDeploymentTasks('post-deploy', $this->getConfig(), 'Post-Deployment'); } // Time Information Hosts if ($this->hostsCount > 0) { $timeTextHost = $this->transcurredTime($this->endTimeHosts - $this->startTimeHosts); Console::output('Time for deployment: ' . $timeTextHost . '.'); $timeTextPerHost = $this->transcurredTime(round(($this->endTimeHosts - $this->startTimeHosts) / $this->hostsCount)); Console::output('Average time per host: ' . $timeTextPerHost . '.'); } // Time Information General $timeText = $this->transcurredTime(time() - $this->startTime); Console::output('Total time: ' . $timeText . '.', 1, 2); // Send Notifications $this->sendNotification(); // Unlock if (file_exists('.mage/~working.lock')) { unlink('.mage/~working.lock'); } } /** * Execute Pre and Post Deployment Tasks * * @param string $stage * @param Config $config * @param string $title */ protected function runNonDeploymentTasks($stage, Config $config, $title) { $tasksToRun = $config->getTasks($stage); self::$failedTasks = 0; // PreDeployment Hook if ($stage == 'pre-deploy') { // 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'); } } // PostDeployment Hook if ($stage == 'post-deploy') { // 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'); } // Remove Remote Source if (is_array($config->deployment('source', null))) { array_push($tasksToRun, 'scm/remove-clone'); } } if (count($tasksToRun) == 0) { Console::output('No ' . $title . ' tasks defined.', 1, 3); } else { Console::output('Starting ' . $title . ' tasks:'); $tasks = 0; $completedTasks = 0; foreach ($tasksToRun as $taskData) { $tasks++; $task = Factory::get($taskData, $config, false, $stage); if ($this->runTask($task)) { $completedTasks++; } else { self::$failedTasks++; } } if ($completedTasks == $tasks) { $tasksColor = 'green'; } else { $tasksColor = 'red'; } Console::output('Finished ' . $title . ' tasks: <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); } } protected function runDeploymentTasks() { if (self::$deployStatus == self::FAILED) { return; } // 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); } 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; } // Set Host and Host Specific Config $this->getConfig()->setHost($host); $this->getConfig()->setHostConfig($hostConfig); // Prepare Tasks $tasks = 0; $completedTasks = 0; Console::output('Deploying to ' . $this->getConfig()->getHost() . ''); $tasksToRun = $this->getConfig()->getTasks(); // Guess a Deploy Strategy switch ($this->getConfig()->deployment('strategy', 'guess')) { case 'rsync': $deployStrategy = 'deployment/strategy/rsync'; 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; } array_unshift($tasksToRun, $deployStrategy); if (count($tasksToRun) == 0) { Console::output('Warning! No Deployment tasks defined.', 2); Console::output('Deployment to ' . $host . ' skipped!', 1, 3); } else { foreach ($tasksToRun as $taskData) { $tasks++; $task = Factory::get($taskData, $this->getConfig(), false, 'deploy'); if ($this->runTask($task)) { $completedTasks++; } else { self::$failedTasks++; } } if ($completedTasks == $tasks) { $tasksColor = 'green'; } else { $tasksColor = 'red'; } Console::output('Deployment to ' . $this->getConfig()->getHost() . ' completed: <' . $tasksColor . '>' . $completedTasks . '/' . $tasks . ' tasks done.', 1, 3); } // Reset Host Config $this->getConfig()->setHostConfig(null); } $this->endTimeHosts = time(); if (self::$failedTasks > 0) { self::$deployStatus = self::FAILED; } else { self::$deployStatus = self::SUCCEDED; } // Releasing if (self::$deployStatus == self::SUCCEDED && $this->getConfig()->release('enabled', false) == true) { // Execute the Releases Console::output('Starting the Releaseing'); foreach ($hosts as $hostKey => $host) { // Check if Host has specific configuration $hostConfig = null; if (is_array($host)) { $hostConfig = $host; $host = $hostKey; } // Set Host $this->getConfig()->setHost($host); $this->getConfig()->setHostConfig($hostConfig); $task = Factory::get('deployment/release', $this->getConfig(), false, 'deploy'); if ($this->runTask($task, 'Releasing on host ' . $host . ' ... ')) { $completedTasks++; } // Reset Host Config $this->getConfig()->setHostConfig(null); } Console::output('Finished the Releaseing', 1, 3); // Execute the Post-Release Tasks foreach ($hosts as $hostKey => $host) { // Check if Host has specific configuration $hostConfig = null; if (is_array($host)) { $hostConfig = $host; $host = $hostKey; } // Set Host $this->getConfig()->setHost($host); $this->getConfig()->setHostConfig($hostConfig); $tasksToRun = $this->getConfig()->getTasks('post-release'); $tasks = count($tasksToRun); $completedTasks = 0; if (count($tasksToRun) > 0) { Console::output('Starting Post-Release tasks for ' . $host . ':'); foreach ($tasksToRun as $task) { $task = Factory::get($task, $this->getConfig(), false, 'post-release'); if ($this->runTask($task)) { $completedTasks++; } } 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); } } } } /** * Runs a Task * * @param string $task * @param string $title * @return boolean */ protected function runTask($task, $title = null) { $task->init(); if ($title == null) { $title = 'Running ' . $task->getName() . ' ... '; } Console::output($title, 2, 0); $runTask = true; if (($task instanceOf SkipOnOverride) && $this->getConfig()->getParameter('overrideRelease', false)) { $runTask == false; } $result = false; if ($runTask == true) { try { $result = $task->run(); if ($result == true) { Console::output('OK', 0); $result = true; } else { Console::output('FAIL', 0); $result = false; } } catch (ErrorWithMessageException $e) { Console::output('FAIL [Message: ' . $e->getMessage() . ']', 0); $result = false; } catch (SkipException $e) { Console::output('SKIPPED', 0); $result = true; } catch (Exception $e) { Console::output('FAIL', 0); $result = false; } } else { Console::output('SKIPPED', 0); $result = true; } return $result; } /** * Humanize Transcurred time * * @param integer $time * @return string */ protected function transcurredTime($time) { $hours = floor($time / 3600); $minutes = floor(($time - ($hours * 3600)) / 60); $seconds = $time - ($minutes * 60) - ($hours * 3600); $timeText = array(); if ($hours > 0) { $timeText[] = $hours . ' hours'; } if ($minutes > 0) { $timeText[] = $minutes . ' minutes'; } if ($seconds >= 0) { $timeText[] = $seconds . ' seconds'; } return implode(' ', $timeText); } /** * Send Email Notification if enabled */ protected function sendNotification() { $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 if (!$projectName || !$projectEmail || !$notificationsEnabled) { return false; } } }