diff --git a/bin/provision-robo.php b/bin/provision-robo.php index 11eb8b8b10206c2600285e01c239067f04c0bb32..d0dc21b85774701e6438693378a7bd591d7b1b78 100644 --- a/bin/provision-robo.php +++ b/bin/provision-robo.php @@ -1,9 +1,5 @@ start(); -$input = new \Symfony\Component\Console\Input\ArgvInput($argv); -$output = new \Aegir\Provision\Console\ConsoleOutput(); +// Create input output objects. +$input = new ArgvInput($argv); +$output = new ConsoleOutput(); + +// Create a config object. + +$config = new DefaultsConfig(); +$config->extend(new YamlConfig($config->get('user_home') . '/.provision.yml')); +$config->extend(new DotEnvConfig(getcwd())); +$config->extend(new EnvConfig()); -$app = new \Aegir\Provision\Provision($input, $output); +// Create the app. +$app = new \Aegir\Provision\Provision($config, $input, $output); $status_code = $app->run($input, $output); diff --git a/src/Application.php b/src/Application.php index 421c7c1168e5acdf9d111adec19269449ae80b77..141396b0ad1693ff836f3b198e1080253c918a53 100644 --- a/src/Application.php +++ b/src/Application.php @@ -72,7 +72,7 @@ class Application extends BaseApplication * * @throws \Exception */ - public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', Provision $provision = NULL) { // If no timezone is set, set Default. if (empty(ini_get('date.timezone'))) { @@ -87,6 +87,7 @@ class Application extends BaseApplication // throw new \Exception($e->getMessage()); // } + $this->provision = $provision; parent::__construct($name, $version); } @@ -109,31 +110,15 @@ class Application extends BaseApplication ); } - /** - * @var Config - */ - private $config; - /** * Getter for Configuration. * - * @return Config + * @return \Aegir\Provision\Console\ProvisionConfig * Configuration object. */ public function getConfig() { - return $this->config; - } - - /** - * Setter for Configuration. - * - * @param Config $config - * Configuration object. - */ - public function setConfig(Config $config) - { - $this->config = $config; + return $this->provision->getConfig(); } /** diff --git a/src/Command/StatusCommand.php b/src/Command/StatusCommand.php index c0c4625da8c7f05aaa808c1eeb3e6551304ec165..9e8f44b82a6b7be23e57315c9acdb8cb0d8e7f7c 100644 --- a/src/Command/StatusCommand.php +++ b/src/Command/StatusCommand.php @@ -58,8 +58,9 @@ class StatusCommand extends Command } else { $headers = ['Provision CLI Configuration']; - $rows = [['Configuration File', $this->getApplication()->getConfig()->getConfigPath()]]; - $config = $this->getApplication()->getConfig()->all(); + $rows = []; + $config = $this->getApplication()->getConfig()->toArray(); + unset($config['options']); foreach ($config as $key => $value) { $rows[] = [$key, $value]; } diff --git a/src/Console/DefaultsConfig.php b/src/Console/DefaultsConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..50ef0ce779cf016b5a730b26b69063e97a45def9 --- /dev/null +++ b/src/Console/DefaultsConfig.php @@ -0,0 +1,165 @@ +set('root', $this->getProvisionRoot()); + $this->set('php', $this->getPhpBinary()); + $this->set('php_version', PHP_VERSION); + $this->set('php_ini', get_cfg_var('cfg_file_path')); + $this->set('script', $this->getProvisionScript()); + $this->set('os_version', php_uname('v')); + $this->set('user_home', $this->getHomeDir()); + $this->set('aegir_root', $this->getHomeDir()); + $this->set('script_user', $this->getScriptUser()); + $this->set('config_path', $this->getHomeDir() . '/config'); + + $this->validateConfig(); + } + + /** + * Check configuration values against the current system. + * + * @throws \Exception + */ + protected function validateConfig() { + // Check that aegir_root is writable. + // @TODO: Create some kind of Setup functionality. + if (!is_writable($this->config->get('aegir_root'))) { + throw new \Exception( + "There is an error with your configuration. The folder set to 'aegir_root' ({$this->config['aegir_root']}) is not writable. Fix this or change the aegir_root value in the file {$this->config_path}." + ); + } + // Check that config_path exists and is writable. + if (!file_exists($this->config->get('config_path'))) { + throw new \Exception( + "There is an error with your configuration. The folder set to 'config_path' ({$this->config['config_path']}) does not exist. Create it or change the config_path value in the file {$this->config_path}." + ); + } + elseif (!is_writable($this->config->get('config_path'))) { + throw new \Exception( + "There is an error with your configuration. The folder set to 'config_path' ({$this->config['config_path']}) is not writable. Fix this or change the config_path value in the file {$this->config_path}." + ); + } + elseif (!file_exists($this->config->get('config_path') . '/provision')) { + mkdir($this->config->get('config_path') . '/provision'); + } + + // Ensure that script_user is the user. + $real_script_user = $this->getScriptUser(); + if ($this->config->get('script_user') != $real_script_user) { + throw new \Exception( + "There is an error with your configuration. The user set as 'script_user' ({$this->config['script_user']}) is not the currently running user ({$real_script_user}). Change to user {$this->config->get('script_user')} or change the script_user value in the file {$this->config_path}." + ); + } + } + + + /** + * Get the name of the source for this configuration object. + * + * @return string + */ + public function getSourceName() + { + return 'Default'; + } + + /** + * Returns location of PHP with which to run Terminus + * + * @return string + */ + protected function getPhpBinary() + { + return defined('PHP_BINARY') ? PHP_BINARY : 'php'; + } + + /** + * Finds and returns the root directory of Provision + * + * @param string $current_dir Directory to start searching at + * @return string + * @throws \Exception + */ + protected function getProvisionRoot($current_dir = null) + { + if (is_null($current_dir)) { + $current_dir = dirname(__DIR__); + } + if (file_exists($current_dir . DIRECTORY_SEPARATOR . 'composer.json')) { + return $current_dir; + } + $dir = explode(DIRECTORY_SEPARATOR, $current_dir); + array_pop($dir); + if (empty($dir)) { + throw new \Exception('Could not locate root to set PROVISION_ROOT.'); + } + $dir = implode(DIRECTORY_SEPARATOR, $dir); + $root_dir = $this->getProvisionRoot($dir); + return $root_dir; + } + + /** + * Finds and returns the name of the script running Terminus functions + * + * @return string + */ + protected function getProvisionScript() + { + $debug = debug_backtrace(); + $script_location = array_pop($debug); + $script_name = str_replace( + $this->getProvisionRoot() . DIRECTORY_SEPARATOR, + '', + $script_location['file'] + ); + return $script_name; + } + + /** + * Returns the appropriate home directory. + * + * Adapted from Terminus Package Manager by Ed Reel + * @author Ed Reel <@uberhacker> + * @url https://github.com/uberhacker/tpm + * + * @return string + */ + protected function getHomeDir() + { + $home = getenv('HOME'); + if (!$home) { + $system = ''; + if (getenv('MSYSTEM') !== null) { + $system = strtoupper(substr(getenv('MSYSTEM'), 0, 4)); + } + if ($system != 'MING') { + $home = getenv('HOMEPATH'); + } + } + return $home; + } + + /** + * Determine the user running provision. + */ + protected function getScriptUser() { + $real_script_user = posix_getpwuid(posix_geteuid()); + return $real_script_user['name']; + } +} diff --git a/src/Console/DotEnvConfig.php b/src/Console/DotEnvConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..b6c8312b46a94b50bddf8556ea6e65b258610761 --- /dev/null +++ b/src/Console/DotEnvConfig.php @@ -0,0 +1,39 @@ +setSourceName($file); + + // Load environment variables from __DIR__/.env + if (file_exists($file)) { + // Remove comments (which start with '#') + $lines = file($file); + $lines = array_filter($lines, function ($line) { + return strpos(trim($line), '#') !== 0; + }); + $info = parse_ini_string(implode($lines, "\n")); + $this->fromArray($info); + } + } +} diff --git a/src/Console/EnvConfig.php b/src/Console/EnvConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..86c642a5e0ea4d330758cb22722f45f5693bbfe8 --- /dev/null +++ b/src/Console/EnvConfig.php @@ -0,0 +1,29 @@ + $val) { + if ($this->keyIsConstant($key)) { + $this->set($key, $val); + } + } + } + } +} diff --git a/src/Console/ProvisionConfig.php b/src/Console/ProvisionConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..ccdd7a632d88fcaccd66e54cdd5f619ca7a453fa --- /dev/null +++ b/src/Console/ProvisionConfig.php @@ -0,0 +1,240 @@ + $val) { + $this->set($key, $val); + } + } + + /** + * Convert the config to an array. + * + * @return array + */ + public function toArray() + { + $out = []; + foreach ($this->keys() as $key) { + $out[$key] = $this->get($key); + } + return $out; + } + + /** + * Set a config value. Converts key from Provision constant (PROVISION_XXX) if needed. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + // Convert Provision constant name to internal key. + if ($this->keyIsConstant($key)) { + $key = $this->getKeyFromConstant($key); + } + return parent::set($key, $value); + } + + /** + * Get a configuration value + * + * @param string $key Which config item to look up + * @param string|null $defaultOverride Override usual default value with a different default + * + * @return mixed + */ + public function get($key, $defaultOverride = null) + { + $value = parent::get($key, $defaultOverride); + // Replace placeholders. + if (is_string($value)) { + $value = $this->replacePlaceholders($value); + } + return $value; + } + + /** + * Return all of the keys in the Config + * @return array + */ + public function keys() + { + return array_keys($this->export()); + } + + /** + * Override the values in this Config object with the given input Config + * + * @param \Aegir\Provision\Console\Config $in + */ + public function extend(ProvisionConfig $in) + { + foreach ($in->keys() as $key) { + $this->set($key, $in->get($key)); + // Set the source of this variable to make tracking config easier. + $this->setSource($key, $in->getSource($key)); + } + } + + /** + * Get the name of the source for this configuration object. + * + * @return string + */ + public function getSourceName() + { + return $this->source_name; + } + + /** + * Get a description of where this configuration came from. + * + * @param $key + * @return string + */ + public function getSource($key) + { + return isset($this->sources[$key]) ? $this->sources[$key] : $this->getSourceName(); + } + + /** + * Set the source for a given configuration item. + * + * @param $key + * @param $source + */ + protected function setSource($key, $source) + { + $this->sources[$key] = $source; + } + + /** + * Reflects a key name given a PRovision constant name + * + * @param string $constant_name The name of a constant to get a key for + * @return string + */ + protected function getKeyFromConstant($constant_name) + { + $key = strtolower(str_replace($this->constant_prefix, '', $constant_name)); + return $key; + } + + /** + * Turn an internal key into a constant name + * + * @param string $key The key to get the constant name for. + * @return string + */ + public function getConstantFromKey($key) + { + $key = strtoupper($this->constant_prefix . $key); + return $key; + } + + /** + * Determines if a key is a Provision constant name + * + * @param $key + * @return boolean + */ + protected function keyIsConstant($key) + { + return strpos($key, $this->constant_prefix) === 0; + } + + /** + * Exchanges values in [[ ]] in the given string with constants + * + * @param string $string The string to perform replacements on + * @return string $string The modified string + */ + protected function replacePlaceholders($string) + { + $regex = '~\[\[(.*?)\]\]~'; + preg_match_all($regex, $string, $matches); + if (!empty($matches)) { + foreach ($matches[1] as $id => $value) { + $replacement_key = $this->getKeyFromConstant(trim($value)); + $replacement = $this->get($replacement_key); + if ($replacement) { + $string = str_replace($matches[0][$id], $replacement, $string); + } + } + } + $fixed_string = $this->fixDirectorySeparators($string); + return $fixed_string; + } + + /** + * Ensures a directory exists + * + * @param string $name The name of the config var + * @param string $value The value of the named config var + * @return boolean|null + */ + public function ensureDirExists($name, $value) + { + if ((strpos($name, $this->constant_prefix) !== false) && (strpos($name, '_DIR') !== false) && ($value != '~')) { + try { + $dir_exists = (is_dir($value) || (!file_exists($value) && @mkdir($value, 0777, true))); + } catch (\Exception $e) { + return false; + } + return $dir_exists; + } + return null; + } + + /** + * Ensures that directory paths work in any system + * + * @param string $path A path to set the directory separators for + * @return string + */ + public function fixDirectorySeparators($path) + { + return str_replace(['/', '\\',], DIRECTORY_SEPARATOR, $path); + } + + /** + * @param mixed $source_name + */ + public function setSourceName($source_name) + { + $this->source_name = $source_name; + } +} diff --git a/src/Console/YamlConfig.php b/src/Console/YamlConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..cafc09db34246886a4d712c3d8dec1edb98c3a81 --- /dev/null +++ b/src/Console/YamlConfig.php @@ -0,0 +1,29 @@ +setSourceName($yml_path); + $file_config = file_exists($yml_path) ? Yaml::parse(file_get_contents($yml_path)) : []; + if (!is_null($file_config)) { + $this->fromArray($file_config); + } + } +} diff --git a/src/Provision.php b/src/Provision.php index df8e2f9ae4c2bfd8192a294f1c5d5235de37e573..d78addb89c57e07e5f884401dbec2a90459f593c 100644 --- a/src/Provision.php +++ b/src/Provision.php @@ -4,21 +4,24 @@ namespace Aegir\Provision; use Aegir\Provision\Commands\ExampleCommands; -use Aegir\Provision\Console\Config as ConsoleConfig; use Consolidation\Config\Loader\ConfigProcessor; use League\Container\Container; +use League\Container\ContainerAwareInterface; use League\Container\ContainerAwareTrait; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; use Robo\Collection\CollectionBuilder; use Robo\Common\ConfigAwareTrait; use Robo\Config\Config; +use Robo\Contract\ConfigAwareInterface; use Robo\Robo; use Robo\Runner as RoboRunner; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -class Provision { +class Provision implements ConfigAwareInterface, ContainerAwareInterface, LoggerAwareInterface { const APPLICATION_NAME = 'Aegir Provision'; const VERSION = '4.x-dev'; @@ -26,28 +29,36 @@ class Provision { use ConfigAwareTrait; use ContainerAwareTrait; + use LoggerAwareTrait; - public $runner; + /** + * @var \Robo\Runner + */ + private $runner; + + /** + * @var string[] + */ + private $commands = []; public function __construct( -// Config $config, + Config $config, InputInterface $input = NULL, OutputInterface $output = NULL ) { - - // Prepare Console configuration and import it into Robo config. - $consoleConfig = new \Aegir\Provision\Console\Config(); - - $config = new Config(); - $config->import($consoleConfig->all()); +// +// // Prepare Console configuration and import it into Robo config. +// $consoleConfig = new \Aegir\Provision\Console\Config(); +// +// $config = new Config(); +// $config->import($consoleConfig->all()); $this->setConfig($config); // Create Application. - $application = new \Aegir\Provision\Application(self::APPLICATION_NAME, self::VERSION); - $application->provision = $this; + $application = new \Aegir\Provision\Application(self::APPLICATION_NAME, self::VERSION, $this); $application->console = $output; - $application->setConfig($consoleConfig); +// $application->setConfig($consoleConfig); // Create and configure container. $container = Robo::createDefaultContainer($input, $output, $application, $config);