/** * Links the release being deployed. * * @param DeployReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher */ public function onDeployOrRollbackReleaseLinkRelease(DeployReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $release = $event->getRelease(); $host = $release->getWorkspace()->getHost(); $connection = $this->ensureConnection($host); $releasePath = $host->getPath() . '/' . $host->getStage(); $context = array('linkTarget' => $releasePath, 'releaseVersion' => $release->getVersion(), 'event.task.action' => TaskInterface::ACTION_IN_PROGRESS); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Linking "{linkTarget}" to release "{releaseVersion}".', $eventName, $this, $context)); if ($connection->isLink($releasePath) === false || $connection->readLink($releasePath) !== $release->getPath()) { if ($connection->isLink($releasePath)) { $connection->delete($releasePath, false); } if ($connection->link($release->getPath(), $releasePath)) { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Linked "{linkTarget}" to release "{releaseVersion}".', $eventName, $this, $context)); } else { $context['event.task.action'] = TaskInterface::ACTION_FAILED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Linking "{linkTarget}" to release "{releaseVersion}" failed.', $eventName, $this, $context)); throw new TaskRuntimeException(sprintf('Linking "%s" to release "%s" failed.', $context['linkTarget'], $context['releaseVersion']), $this); } } else { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Link "{linkTarget}" to release "{releaseVersion}" already exists.', $eventName, $this, $context)); } }
/** * Executes the configured command. * * @param Event $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException when execution of the command has failed. */ public function onEvent(Event $event, $eventName, EventDispatcherInterface $eventDispatcher) { if (in_array($eventName, $this->events)) { if ($event instanceof ReleaseEvent) { $release = $event->getRelease(); $host = $release->getWorkspace()->getHost(); $path = $release->getPath(); } else { $host = $event->getWorkspace()->getHost(); $path = $host->getPath(); } $connection = $this->ensureConnection($host); $currentWorkingDirectory = $connection->getWorkingDirectory(); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Executing command "{command}".', $eventName, $this, array('command' => $this->command, 'event.task.action' => TaskInterface::ACTION_IN_PROGRESS))); $connection->changeWorkingDirectory($path); $result = $connection->executeCommand($this->command, $this->arguments); $connection->changeWorkingDirectory($currentWorkingDirectory); if ($result->isSuccessful()) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Executed command "{command}".', $eventName, $this, array('command' => $this->command, 'event.task.action' => TaskInterface::ACTION_COMPLETED, 'output.resetLine' => true))); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::DEBUG, "{separator} Command output:{separator}\n{command.result}{separator}", $eventName, $this, array('command.result' => $result->getOutput(), 'separator' => "\n=================\n"))); } else { throw new TaskCommandExecutionException(sprintf('Failed executing command "%s".', $this->command), $result, $this); } } }
/** * Creates a checkout of the repository for the release. * * @param PrepareReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException when the checkout of the repository has failed. */ public function onPrepareReleaseCheckoutRepository(PrepareReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $context = array('repositoryUrl' => $this->repositoryUrl, 'version' => $event->getRelease()->getVersion(), 'event.task.action' => TaskInterface::ACTION_IN_PROGRESS); $connection = $this->ensureConnection($event->getRelease()->getWorkspace()->getHost()); $processExecutor = new ConnectionAdapterProcessExecutor($connection); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Creating checkout of repository "{repositoryUrl}" for version "{version}".', $eventName, $this, $context)); $repository = new Repository($this->repositoryUrl, $event->getRelease()->getPath(), $processExecutor); if ($repository->checkout($event->getRelease()->getVersion())) { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Created checkout of repository "{repositoryUrl}" for version "{version}".', $eventName, $this, $context)); } else { throw new TaskCommandExecutionException(sprintf('Failed to checkout version "%s" from repository "%s".', $event->getRelease()->getVersion(), $this->repositoryUrl), $processExecutor->getLastProcessExecutionResult(), $this); } }
/** * Sets the correct permissions and group for the configured path. * * @param InstallReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException */ public function onInstallReleaseUpdateFilePermissions(InstallReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $host = $event->getRelease()->getWorkspace()->getHost(); $connection = $this->ensureConnection($host); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Updating permissions for the configured paths...', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_IN_PROGRESS))); $releasePath = $event->getRelease()->getPath(); $result = true; foreach ($this->paths as $path => $pathSettings) { $result = $result && $this->updateFilePermissions($connection, $releasePath, $path, $pathSettings); } if ($result === true) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Updated permissions for the configured paths.', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_COMPLETED, 'output.resetLine' => true))); } else { throw new TaskRuntimeException('Failed updating the permissions for the configured paths.', $this); } }
/** * Saves a YAML configuration file to a path within the release. * * @param InstallReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher */ public function onInstallReleaseCreateOrUpdateConfiguration(InstallReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $release = $event->getRelease(); $this->gatherEnvironmentVariables($release); $connection = $this->ensureConnection($release->getWorkspace()->getHost()); $configurationFile = $release->getPath() . $this->configurationFile; $configurationDistributionFile = $configurationFile . '.dist'; $context = array('action' => 'Creating', 'configurationFile' => $configurationFile, 'event.task.action' => TaskInterface::ACTION_IN_PROGRESS); if ($connection->isFile($configurationFile)) { $context['action'] = 'Updating'; } $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, '{action} configuration file "{configurationFile}"...', $eventName, $this, $context)); $yamlConfiguration = $this->getYamlConfiguration($connection, $release->getWorkspace()->getHost()->getStage(), $configurationFile, $configurationDistributionFile); if ($connection->putContents($configurationFile, $yamlConfiguration)) { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; if ($context['action'] === 'Creating') { $context['action'] = 'Created'; } else { $context['action'] = 'Updated'; } $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, '{action} configuration file "{configurationFile}".', $eventName, $this, $context)); } else { $context['event.task.action'] = TaskInterface::ACTION_FAILED; $context['action'] = strtolower($context['action']); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::WARNING, 'Failed {action} configuration file "{configurationFile}".', $eventName, $this, $context)); } }
/** * {@inheritdoc} */ public function deploy($version, $stage) { $successfulDeploy = true; $hosts = $this->configuration->getHostsByStage($stage); foreach ($hosts as $host) { $exception = null; $deployEventName = AccompliEvents::DEPLOY_RELEASE; $deployCompleteEventName = AccompliEvents::DEPLOY_RELEASE_COMPLETE; $deployFailedEventName = AccompliEvents::DEPLOY_RELEASE_FAILED; $title = new Title($this->logger->getOutput(), sprintf('Deploying release "%s" to "%s":', $version, $host->getHostname())); $title->render(); try { $this->eventDispatcher->dispatch(AccompliEvents::CREATE_CONNECTION, new HostEvent($host)); $workspaceEvent = new WorkspaceEvent($host); $this->eventDispatcher->dispatch(AccompliEvents::GET_WORKSPACE, $workspaceEvent); $workspace = $workspaceEvent->getWorkspace(); if ($workspace instanceof Workspace) { $prepareDeployReleaseEvent = new PrepareDeployReleaseEvent($workspace, $version); $this->eventDispatcher->dispatch(AccompliEvents::PREPARE_DEPLOY_RELEASE, $prepareDeployReleaseEvent); $release = $prepareDeployReleaseEvent->getRelease(); if ($release instanceof Release) { $currentRelease = $prepareDeployReleaseEvent->getCurrentRelease(); if ($currentRelease instanceof Release && Comparator::lessThan($release->getVersion(), $currentRelease->getVersion())) { $deployEventName = AccompliEvents::ROLLBACK_RELEASE; $deployCompleteEventName = AccompliEvents::ROLLBACK_RELEASE_COMPLETE; $deployFailedEventName = AccompliEvents::ROLLBACK_RELEASE_FAILED; } $deployReleaseEvent = new DeployReleaseEvent($release, $currentRelease); $this->eventDispatcher->dispatch($deployEventName, $deployReleaseEvent); $this->eventDispatcher->dispatch($deployCompleteEventName, $deployReleaseEvent); continue; } throw new RuntimeException(sprintf('No task configured to initialize release version "%s" for deployment.', $version)); } throw new RuntimeException('No task configured to initialize the workspace.'); } catch (Exception $exception) { } $successfulDeploy = false; $failedEvent = new FailedEvent($this->eventDispatcher->getLastDispatchedEventName(), $this->eventDispatcher->getLastDispatchedEvent(), $exception); $this->eventDispatcher->dispatch($deployFailedEventName, $failedEvent); } return $successfulDeploy; }
/** * Creates the workspace directories when not already existing. * * @param WorkspaceEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException when the workspace path doesn't exist and can't be created. */ public function onPrepareWorkspaceCreateWorkspace(WorkspaceEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $workspace = $event->getWorkspace(); $connection = $this->ensureConnection($workspace->getHost()); $workspacePath = $workspace->getHost()->getPath(); if ($connection->isDirectory($workspacePath) === false && $connection->createDirectory($workspacePath) === false) { throw new TaskRuntimeException(sprintf('The workspace path "%s" does not exist and could not be created.', $workspacePath), $this); } $directories = array_merge(array($workspace->getReleasesDirectory(), $workspace->getDataDirectory(), $workspace->getCacheDirectory()), $workspace->getOtherDirectories()); foreach ($directories as $directory) { $context = array('directory' => $directory, 'event.task.action' => TaskInterface::ACTION_IN_PROGRESS); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Creating directory "{directory}".', $eventName, $this, $context)); if ($connection->isDirectory($directory) === false) { if ($connection->createDirectory($directory)) { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Created directory "{directory}".', $eventName, $this, $context)); } else { $context['event.task.action'] = TaskInterface::ACTION_FAILED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::WARNING, 'Failed creating directory "{directory}".', $eventName, $this, $context)); } } else { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Directory "{directory}" exists.', $eventName, $this, $context)); } } }
/** * Links the maintenance page to the stage being deployed. * * @param PrepareDeployReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException when not able to link the maintenance page. */ public function onPrepareDeployReleaseLinkMaintenancePageToStage(PrepareDeployReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { if (VersionCategoryComparator::matchesStrategy($this->strategy, $event->getRelease(), $event->getCurrentRelease()) === false) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Skipped linking maintenance page according to strategy.', $eventName, $this)); return; } $host = $event->getWorkspace()->getHost(); $connection = $this->ensureConnection($host); $linkSource = $host->getPath() . '/maintenance/'; $linkTarget = $host->getPath() . '/' . $host->getStage(); $context = array('linkTarget' => $linkTarget); if ($connection->isLink($linkTarget) && $connection->delete($linkTarget, false) === false) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::WARNING, 'Failed to remove existing "{linkTarget}" link.', $eventName, $this, $context)); } $context['event.task.action'] = TaskInterface::ACTION_IN_PROGRESS; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Linking "{linkTarget}" to maintenance page.', $eventName, $this, $context)); if ($connection->link($linkSource, $linkTarget)) { $context['event.task.action'] = TaskInterface::ACTION_COMPLETED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Linked "{linkTarget}" to maintenance page.', $eventName, $this, $context)); } else { $context['event.task.action'] = TaskInterface::ACTION_FAILED; $context['output.resetLine'] = true; $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Linking "{linkTarget}" to maintenance page failed.', $eventName, $this, $context)); throw new TaskRuntimeException(sprintf('Linking "%s" to maintenance page failed.', $context['linkTarget']), $this); } }
/** * Adds an SSH key to the initialized SSH agent. * * @param Workspace $workspace * @param ConnectionAdapterInterface $connection * @param string $key * @param string $eventName * @param EventDispatcherInterface $eventDispatcher */ private function addKeyToSSHAgent(Workspace $workspace, ConnectionAdapterInterface $connection, $key, $eventName, EventDispatcherInterface $eventDispatcher) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Adding key to SSH agent...', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_IN_PROGRESS))); $keyFile = $workspace->getHost()->getPath() . '/tmp.key'; if ($connection->createFile($keyFile, 0700) && $connection->putContents($keyFile, $key)) { $result = $connection->executeCommand('ssh-add', array($keyFile)); if ($result->isSuccessful()) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Added key to SSH agent.', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_COMPLETED, 'output.resetLine' => true))); } $connection->delete($keyFile); if ($result->isSuccessful()) { return; } } $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::INFO, 'Failed adding key to SSH agent.', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_FAILED, 'output.resetLine' => true))); }
/** * Runs the Composer install command to install the dependencies for the release. * * @param InstallReleaseEvent $event * @param string $eventName * @param EventDispatcherInterface $eventDispatcher * * @throws TaskRuntimeException */ public function onInstallReleaseExecuteComposerInstall(InstallReleaseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher) { $release = $event->getRelease(); $host = $release->getWorkspace()->getHost(); $connection = $this->ensureConnection($host); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Installing Composer dependencies.', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_IN_PROGRESS))); $authenticationFile = $release->getPath() . '/auth.json'; if (empty($this->authentication) === false) { $connection->putContents($authenticationFile, json_encode($this->authentication)); } $connection->changeWorkingDirectory($host->getPath()); $result = $connection->executeCommand(sprintf('php composer.phar install --no-interaction --working-dir="%s" --no-dev --no-scripts --optimize-autoloader', $release->getPath())); if ($connection->isFile($authenticationFile)) { $connection->delete($authenticationFile); } if ($result->isSuccessful()) { $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::NOTICE, 'Installed Composer dependencies.', $eventName, $this, array('event.task.action' => TaskInterface::ACTION_COMPLETED, 'output.resetLine' => true))); $eventDispatcher->dispatch(AccompliEvents::LOG, new LogEvent(LogLevel::DEBUG, "{separator} Command output:{separator}\n{command.result}{separator}", $eventName, $this, array('command.result' => $result->getOutput(), 'separator' => "\n=================\n"))); } else { throw new TaskCommandExecutionException('Failed installing Composer dependencies.', $result, $this); } }