magallanes/src/Runtime/Runtime.php

560 lines
14 KiB
PHP
Raw Normal View History

2016-12-31 07:52:25 +01:00
<?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\Runtime;
2017-01-12 02:22:21 +01:00
use Mage\Deploy\Strategy\ReleasesStrategy;
use Mage\Deploy\Strategy\RsyncStrategy;
use Mage\Deploy\Strategy\StrategyInterface;
2016-12-31 07:52:25 +01:00
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Process\Process;
2017-01-07 05:53:57 +01:00
use Mage\Runtime\Exception\RuntimeException;
2016-12-31 07:52:25 +01:00
/**
* Runtime is a container of all run in time configuration, stages of progress, hosts being deployed, etc.
*
* @author Andrés Montañez <andresmontanez@gmail.com>
*/
2017-01-01 05:33:34 +01:00
class Runtime
2016-12-31 07:52:25 +01:00
{
2017-01-01 05:33:34 +01:00
const PRE_DEPLOY = 'pre-deploy';
const ON_DEPLOY = 'on-deploy';
const POST_DEPLOY = 'post-deploy';
const ON_RELEASE = 'on-release';
const POST_RELEASE = 'post-release';
2016-12-31 07:52:25 +01:00
/**
* @var array Magallanes configuration
*/
2017-01-01 22:20:43 +01:00
protected $configuration = [];
2016-12-31 07:52:25 +01:00
/**
* @var string|null Environment being deployed
*/
protected $environment;
/**
* @var string Stage of Deployment
*/
protected $stage;
/**
* @var string|null The host being deployed to
*/
2017-01-24 21:28:37 +01:00
protected $workingHost = null;
2016-12-31 07:52:25 +01:00
/**
* @var string|null The Release ID
*/
protected $releaseId = null;
/**
* @var array Hold a bag of variables for sharing information between tasks, if needed
*/
protected $vars = [];
/**
* @var LoggerInterface|null The logger instance
*/
protected $logger;
/**
* @var bool Indicates if a Rollback operation is in progress
*/
protected $rollback = false;
2021-02-20 21:16:24 +01:00
public function isWindows()
{
return stripos(PHP_OS, 'WIN') === 0;
}
2017-01-01 05:28:58 +01:00
/**
* Generate the Release ID
*
2017-01-01 05:33:34 +01:00
* @return Runtime
2017-01-01 05:28:58 +01:00
*/
public function generateReleaseId()
{
$this->setReleaseId(date('YmdHis'));
return $this;
}
2016-12-31 07:52:25 +01:00
/**
* Sets the Release ID
*
* @param string $releaseId Release ID
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setReleaseId($releaseId)
{
$this->releaseId = $releaseId;
return $this;
}
/**
* Retrieve the current Release ID
*
* @return null|string Release ID
*/
public function getReleaseId()
{
return $this->releaseId;
}
/**
* Sets the Runtime in Rollback mode On or Off
*
* @param bool $inRollback
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setRollback($inRollback)
{
$this->rollback = $inRollback;
return $this;
}
/**
* Indicates if Runtime is in rollback
*
* @return bool
*/
public function inRollback()
{
return $this->rollback;
}
/**
* Sets a value in the Vars bag
*
2017-01-08 06:38:21 +01:00
* @param string $key Variable name
* @param string $value Variable value
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setVar($key, $value)
{
$this->vars[$key] = $value;
return $this;
}
/**
* Retrieve a value from the Vars bag
*
2017-01-08 06:38:21 +01:00
* @param string $key Variable name
2017-01-24 21:32:01 +01:00
* @param mixed $default Variable default value, returned if not found
2017-01-08 06:38:21 +01:00
* @return string
2016-12-31 07:52:25 +01:00
*/
public function getVar($key, $default = null)
{
if (array_key_exists($key, $this->vars)) {
return $this->vars[$key];
}
return $default;
}
/**
* Sets the Logger instance
*
* @param LoggerInterface $logger Logger instance
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setLogger(LoggerInterface $logger = null)
{
$this->logger = $logger;
return $this;
}
/**
* Sets the Magallanes Configuration to the Runtime
*
* @param array $configuration Configuration
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setConfiguration($configuration)
{
$this->configuration = $configuration;
return $this;
}
/**
* Retrieve the Configuration
*
* @return array
*/
public function getConfiguration()
{
return $this->configuration;
}
/**
2017-01-12 02:22:21 +01:00
* Retrieves the Configuration Option for a specific section in the configuration
2016-12-31 07:52:25 +01:00
*
2017-01-08 06:25:40 +01:00
* @param string $key Section name
2016-12-31 07:52:25 +01:00
* @param mixed $default Default value
* @return mixed
*/
2017-01-12 02:22:21 +01:00
public function getConfigOption($key, $default = null)
2016-12-31 07:52:25 +01:00
{
if (array_key_exists($key, $this->configuration)) {
return $this->configuration[$key];
}
return $default;
}
/**
2017-01-12 02:22:21 +01:00
* Returns the Configuration Option for a specific section the current Environment
*
* @param string $key Section/Parameter name
* @param mixed $default Default value
* @return mixed
*/
2017-01-12 02:22:21 +01:00
public function getEnvOption($key, $default = null)
{
if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
return $default;
}
if (!array_key_exists($this->environment, $this->configuration['environments'])) {
return $default;
2016-12-31 07:52:25 +01:00
}
if (array_key_exists($key, $this->configuration['environments'][$this->environment])) {
return $this->configuration['environments'][$this->environment][$key];
}
return $default;
2016-12-31 07:52:25 +01:00
}
/**
* Shortcut to get the the configuration option for a specific environment and merge it with
* the global one (environment specific overrides the global one if present).
*
2017-02-03 08:43:24 +01:00
* @param $key
* @param array $defaultEnv
*
* @return array
*/
2017-02-03 08:43:24 +01:00
public function getMergedOption($key, $defaultEnv = [])
{
2017-02-03 08:43:24 +01:00
$userGlobalOptions = $this->getConfigOption($key, $defaultEnv);
$userEnvOptions = $this->getEnvOption($key, $defaultEnv);
return array_merge(
(is_array($userGlobalOptions) ? $userGlobalOptions : []),
(is_array($userEnvOptions) ? $userEnvOptions : [])
);
}
/**
2017-01-12 02:22:21 +01:00
* Overwrites an Environment Configuration Option
*
2017-01-08 06:25:40 +01:00
* @param string $key
* @param mixed $value
* @return Runtime
*/
2017-01-12 02:22:21 +01:00
public function setEnvOption($key, $value)
{
if (array_key_exists('environments', $this->configuration) && is_array($this->configuration['environments'])) {
if (array_key_exists($this->environment, $this->configuration['environments'])) {
$this->configuration['environments'][$this->environment][$key] = $value;
}
}
return $this;
}
2016-12-31 07:52:25 +01:00
/**
* Sets the working Environment
*
* @param string $environment Environment name
2017-01-01 05:33:34 +01:00
* @return Runtime
2017-01-07 05:53:57 +01:00
* @throws RuntimeException
2016-12-31 07:52:25 +01:00
*/
public function setEnvironment($environment)
{
if (array_key_exists('environments', $this->configuration) && array_key_exists($environment, $this->configuration['environments'])) {
$this->environment = $environment;
return $this;
}
2017-01-07 05:53:57 +01:00
throw new RuntimeException(sprintf('The environment "%s" does not exists.', $environment), 100);
2016-12-31 07:52:25 +01:00
}
/**
* Returns the current working Environment
*
* @return null|string
*/
public function getEnvironment()
{
return $this->environment;
}
/**
* Sets the working stage
*
* @param string $stage Stage code
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setStage($stage)
{
$this->stage = $stage;
return $this;
}
/**
2017-01-10 03:45:30 +01:00
* Retrieve the current working Stage
2016-12-31 07:52:25 +01:00
*
* @return string
*/
public function getStage()
{
return $this->stage;
}
/**
* Retrieve the defined Tasks for the current Environment and Stage
*
* @return array
*/
public function getTasks()
{
2017-01-12 02:22:21 +01:00
if (!array_key_exists('environments', $this->configuration) || !is_array($this->configuration['environments'])) {
return [];
}
if (!array_key_exists($this->environment, $this->configuration['environments'])) {
return [];
}
if (array_key_exists($this->stage, $this->configuration['environments'][$this->environment])) {
if (is_array($this->configuration['environments'][$this->environment][$this->stage])) {
return $this->configuration['environments'][$this->environment][$this->stage];
2016-12-31 07:52:25 +01:00
}
}
return [];
}
/**
* Sets the working Host
*
* @param string $host Host name
2017-01-01 05:33:34 +01:00
* @return Runtime
2016-12-31 07:52:25 +01:00
*/
public function setWorkingHost($host)
{
$this->workingHost = $host;
return $this;
}
/**
* Retrieve the working Host
*
* @return null|string
*/
public function getWorkingHost()
{
return $this->workingHost;
}
/**
* Logs a Message into the Logger
*
* @param string $message Log message
* @param string $level Log Level
*/
public function log($message, $level = LogLevel::DEBUG)
{
if ($this->logger instanceof LoggerInterface) {
$this->logger->log($level, $message);
}
}
/**
* Executes a command, it will be run Locally or Remotely based on the working Stage
*
* @param string $cmd Command to execute
* @param int $timeout Seconds to wait
* @return Process
*/
public function runCommand($cmd, $timeout = 120)
{
switch ($this->getStage()) {
2017-01-01 05:33:34 +01:00
case self::ON_DEPLOY:
case self::ON_RELEASE:
case self::POST_RELEASE:
2016-12-31 07:52:25 +01:00
return $this->runRemoteCommand($cmd, true, $timeout);
default:
return $this->runLocalCommand($cmd, $timeout);
}
}
/**
* Execute a command locally
*
* @param string $cmd Command to execute
* @param int $timeout Seconds to wait
* @return Process
*/
public function runLocalCommand($cmd, $timeout = 120)
{
$this->log($cmd, LogLevel::INFO);
2021-02-11 17:57:40 +01:00
$process = Process::fromShellCommandline($cmd);
2016-12-31 07:52:25 +01:00
$process->setTimeout($timeout);
$process->run();
$this->log($process->getOutput(), LogLevel::DEBUG);
if (!$process->isSuccessful()) {
$this->log($process->getErrorOutput(), LogLevel::ERROR);
}
return $process;
}
/**
* Executes a command remotely, if jail is true, it will run inside the Host Path and the Release (if available)
*
* @param string $cmd Command to execute
* @param bool $jail Jail the command
* @param int $timeout Seconds to wait
* @return Process
*/
public function runRemoteCommand($cmd, $jail, $timeout = 120)
2016-12-31 07:52:25 +01:00
{
$user = $this->getEnvOption('user', $this->getCurrentUser());
2017-01-12 02:22:21 +01:00
$sudo = $this->getEnvOption('sudo', false);
$host = $this->getHostName();
$sshConfig = $this->getSSHConfig();
2016-12-31 07:52:25 +01:00
$cmdDelegate = $cmd;
if ($sudo === true) {
$cmdDelegate = sprintf('sudo %s', $cmd);
}
2017-01-12 02:22:21 +01:00
$hostPath = rtrim($this->getEnvOption('host_path'), '/');
2017-01-24 21:28:37 +01:00
if ($jail && $this->getReleaseId() !== null) {
$cmdDelegate = sprintf('cd %s/releases/%s && %s', $hostPath, $this->getReleaseId(), $cmdDelegate);
} elseif ($jail) {
$cmdDelegate = sprintf('cd %s && %s', $hostPath, $cmdDelegate);
2016-12-31 07:52:25 +01:00
}
$cmdRemote = str_replace('"', '\"', $cmdDelegate);
$cmdLocal = sprintf('ssh -p %d %s %s@%s "%s"', $sshConfig['port'], $sshConfig['flags'], $user, $host, $cmdRemote);
2016-12-31 07:52:25 +01:00
return $this->runLocalCommand($cmdLocal, $timeout);
}
/**
* Get the SSH configuration based on the environment
*
* @return array
*/
public function getSSHConfig()
{
2017-07-22 21:35:28 +02:00
$sshConfig = $this->getEnvOption('ssh', ['port' => 22, 'flags' => '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no']);
2017-07-22 21:35:28 +02:00
if ($this->getHostPort() !== null) {
$sshConfig['port'] = $this->getHostPort();
}
2017-07-22 21:35:28 +02:00
if (!array_key_exists('port', $sshConfig)) {
$sshConfig['port'] = '22';
}
if (!array_key_exists('flags', $sshConfig)) {
$sshConfig['flags'] = '-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
}
2019-08-25 12:23:30 +02:00
if (!array_key_exists('timeout', $sshConfig)) {
$sshConfig['timeout'] = 300;
}
return $sshConfig;
}
2017-01-01 05:28:58 +01:00
/**
* Get the current Host Port or default ssh port
*
* @return integer
*/
public function getHostPort()
{
$info = explode(':', $this->getWorkingHost());
2017-07-22 21:35:28 +02:00
return isset($info[1]) ? $info[1] : null;
}
/**
* Get the current Host Name
*
* @return string
*/
public function getHostName()
{
if (strpos($this->getWorkingHost(), ':') === false) {
return $this->getWorkingHost();
}
$info = explode(':', $this->getWorkingHost());
return $info[0];
}
2017-01-01 05:28:58 +01:00
/**
* Gets a Temporal File name
*
* @return string
*/
public function getTempFile()
{
return tempnam(sys_get_temp_dir(), 'mage');
}
/**
* Get the current user
*
* @return string
*/
public function getCurrentUser()
{
$userData = posix_getpwuid(posix_geteuid());
return $userData['name'];
}
/**
* Shortcut for getting Branch information
*
* @return boolean|string
*/
public function getBranch()
{
2017-01-12 02:22:21 +01:00
return $this->getEnvOption('branch', false);
}
/**
* Guesses the Deploy Strategy to use
*
* @return StrategyInterface
*/
public function guessStrategy()
{
$strategy = new RsyncStrategy();
2017-01-12 02:22:21 +01:00
if ($this->getEnvOption('releases', false)) {
$strategy = new ReleasesStrategy();
}
$strategy->setRuntime($this);
return $strategy;
}
2016-12-31 07:52:25 +01:00
}