summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Pugh2017-11-16 22:19:09 (GMT)
committerJon Pugh2017-11-16 22:19:09 (GMT)
commit76d5ded8cf7f6345956db6d582b63e724976ae02 (patch)
treecf3b0000f3b2a0c6eeedab0619ecfb169cb242cb
parent10bf4473188dde47ca9c93619d97615623fad18c (diff)
Implementing \Robo\Config using Pantheon Terminus as an example.
-rw-r--r--bin/provision-robo.php28
-rw-r--r--src/Application.php23
-rw-r--r--src/Command/StatusCommand.php5
-rw-r--r--src/Console/DefaultsConfig.php165
-rw-r--r--src/Console/DotEnvConfig.php39
-rw-r--r--src/Console/EnvConfig.php29
-rw-r--r--src/Console/ProvisionConfig.php240
-rw-r--r--src/Console/YamlConfig.php29
-rw-r--r--src/Provision.php37
9 files changed, 554 insertions, 41 deletions
diff --git a/bin/provision-robo.php b/bin/provision-robo.php
index 11eb8b8..d0dc21b 100644
--- a/bin/provision-robo.php
+++ b/bin/provision-robo.php
@@ -1,9 +1,5 @@
<?php
-use League\Container\Container;
-use Robo\Robo;
-use Robo\Common\TimeKeeper;
-
// We use PWD if available because getcwd() resolves symlinks, which
// could take us outside of the Drupal root, making it impossible to find.
$cwd = empty($_SERVER['PWD']) ? getcwd() : $_SERVER['PWD'];
@@ -20,14 +16,32 @@ if (file_exists($autoloadFile = __DIR__ . '/vendor/autoload.php')
throw new \Exception("Could not locate autoload.php. cwd is $cwd; __DIR__ is " . __DIR__);
}
+
+use Aegir\Provision\Console\ConsoleOutput;
+use Aegir\Provision\Console\DefaultsConfig;
+use Aegir\Provision\Console\DotEnvConfig;
+use Aegir\Provision\Console\EnvConfig;
+use Aegir\Provision\Console\YamlConfig;
+use Robo\Common\TimeKeeper;
+use Symfony\Component\Console\Input\ArgvInput;
+
// Start Timer.
$timer = new TimeKeeper();
$timer->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 421c7c1..141396b 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);
}
@@ -110,30 +111,14 @@ 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 c0c4625..9e8f44b 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 0000000..50ef0ce
--- /dev/null
+++ b/src/Console/DefaultsConfig.php
@@ -0,0 +1,165 @@
+<?php
+
+namespace Aegir\Provision\Console;
+
+/**
+ * Class DefaultsConfig
+ * @package Aegir\Provision\Console
+ *
+ * Many thanks to pantheon-systems/terminus.
+ */
+class DefaultsConfig extends ProvisionConfig
+{
+ /**
+ * DefaultsConfig constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->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 0000000..b6c8312
--- /dev/null
+++ b/src/Console/DotEnvConfig.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Aegir\Provision\Console;
+
+use Aegir\Provision\Provision;
+
+/**
+ * Class DotEnvConfig
+ * @package Pantheon\Terminus\Config
+ */
+class DotEnvConfig extends ProvisionConfig
+{
+ /**
+ * @var string
+ */
+ protected $file;
+
+ /**
+ * DotEnvConfig constructor.
+ */
+ public function __construct($dir)
+ {
+ parent::__construct();
+
+ $file = $dir . '/.env';
+ $this->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 0000000..86c642a
--- /dev/null
+++ b/src/Console/EnvConfig.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Aegir\Provision\Console;
+
+/**
+ * Class EnvConfig
+ * @package Aegir\Provision\Console
+ */
+class EnvConfig extends ProvisionConfig
+{
+ protected $source_name = 'Environment Variable';
+
+ /**
+ * EnvConfig constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // Add all of the environment vars that match our constant.
+ foreach ([$_SERVER, $_ENV] as $super) {
+ foreach ($super as $key => $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 0000000..ccdd7a6
--- /dev/null
+++ b/src/Console/ProvisionConfig.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace Aegir\Provision\Console;
+
+/**
+ * Class ProvisionConfig
+ * @package Aegir\Provision\Console
+ *
+ * Many thanks to pantheon-systems/terminus.
+ */
+class ProvisionConfig extends \Robo\Config
+{
+ /**
+ * @var string
+ */
+ protected $constant_prefix = 'PROVISION_';
+
+ /**
+ * @var array
+ */
+ protected $sources = [];
+
+ /**
+ * @var string
+ */
+ protected $source_name = 'Unknown';
+
+ /**
+ * Set add all the values in the array to this Config object.
+ }
+ * @param array $array
+ */
+ public function fromArray(array $array = [])
+ {
+ foreach ($array as $key => $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 0000000..cafc09d
--- /dev/null
+++ b/src/Console/YamlConfig.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Aegir\Provision\Console;
+
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * Class YamlConfig
+ * @package Aegir\Provision\Console
+ *
+ * Many thanks to pantheon-systems/terminus.
+ */
+class YamlConfig extends ProvisionConfig
+{
+ /**
+ * YamlConfig constructor.
+ * @param string $yml_path The path to the yaml file.
+ */
+ public function __construct($yml_path)
+ {
+ parent::__construct();
+
+ $this->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 df8e2f9..d78addb 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);