Commit 76d5ded8 authored by Jon Pugh's avatar Jon Pugh

Implementing \Robo\Config using Pantheon Terminus as an example.

parent 10bf4473
<?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);
......
......@@ -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();
}
/**
......
......@@ -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];
}
......
<?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'];
}
}
<?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);
}
}
}
<?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);
}
}
}
}
}
<?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;
}
}
<?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);
}
}
}
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment