Skip to content
Commits on Source (27)
......@@ -16,34 +16,39 @@ 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 Aegir\Provision\Console\Config;
use Drupal\Console\Core\Style\DrupalStyle;
use Robo\Common\TimeKeeper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Exception\CommandNotFoundException;
// Start Timer.
$timer = new TimeKeeper();
$timer->start();
// 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());
// Create the app.
$app = new \Aegir\Provision\Provision($config, $input, $output);
$status_code = $app->run($input, $output);
try {
// Create input output objects.
$input = new ArgvInput($argv);
$output = new ConsoleOutput();
$io = new DrupalStyle($input, $output);
// Create a config object.
$config = new Config();
// Create the app.
$app = new \Aegir\Provision\Provision($config, $input, $output);
// Run the app.
$status_code = $app->run($input, $output);
}
catch (InvalidOptionException $e) {
$io->error("There was a problem with your console configuration: " . $e->getMessage(), 1);
$status_code = 1;
}
// Stop timer.
$timer->stop();
......
......@@ -7,9 +7,11 @@ use Aegir\Provision\Command\ServicesCommand;
use Aegir\Provision\Command\ShellCommand;
use Aegir\Provision\Command\StatusCommand;
use Aegir\Provision\Command\VerifyCommand;
use Aegir\Provision\Common\ProvisionAwareTrait;
use Aegir\Provision\Console\Config;
use Aegir\Provision\Console\ConsoleOutput;
use Drupal\Console\Core\Style\DrupalStyle;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\HelpCommand;
......@@ -20,6 +22,7 @@ use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Command\Command as BaseCommand;
//use Symfony\Component\DependencyInjection\ContainerInterface;
//use Drupal\Console\Annotations\DrupalCommandAnnotationReader;
......@@ -43,21 +46,9 @@ class Application extends BaseApplication
* @var string
*/
const DEFAULT_TIMEZONE = 'America/New_York';
/**
* @var LoggerInterface
*/
public $logger;
/**
* @var DrupalStyle
*/
public $io;
/**
* @var \Aegir\Provision\Provision
*/
public $provision;
use ProvisionAwareTrait;
use LoggerAwareTrait;
/**
* @var ConsoleOutput
......@@ -72,53 +63,25 @@ class Application extends BaseApplication
*
* @throws \Exception
*/
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', Provision $provision = NULL)
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
// If no timezone is set, set Default.
if (empty(ini_get('date.timezone'))) {
date_default_timezone_set($this::DEFAULT_TIMEZONE);
}
//
// // Load Configs
// try {
// $this->config = new Config();
// }
// catch (\Exception $e) {
// throw new \Exception($e->getMessage());
// }
$this->provision = $provision;
parent::__construct($name, $version);
}
/**
* Prepare input and output arguments. Use this to extend the Application object so that $input and $output is fully populated.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function configureIO(InputInterface $input, OutputInterface $output) {
parent::configureIO($input, $output);
$this->io = new DrupalStyle($input, $output);
$this->input = $input;
$this->output = $output;
$this->logger = new ConsoleLogger($output,
[LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL]
);
parent::__construct($name, $version);
}
/**
* Getter for Configuration.
* Make configureIO public so we can run it before ->run()
*
* @return \Aegir\Provision\Console\ProvisionConfig
* Configuration object.
* @param InputInterface $input
* @param OutputInterface $output
*/
public function getConfig()
public function configureIO(InputInterface $input, OutputInterface $output)
{
return $this->provision->getConfig();
parent::configureIO($input, $output);
}
/**
......@@ -136,7 +99,29 @@ class Application extends BaseApplication
return $commands;
}
/**
* Interrupts Command execution to add services like provision and logger.
*
* @param \Symfony\Component\Console\Command\Command $command
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
protected function doRunCommand( BaseCommand $command, InputInterface $input, OutputInterface $output)
{
// Only setProvision if the command is using the trait.
if (method_exists($command, 'setProvision')) {
$command
->setProvision($this->getProvision())
->setLogger($this->logger)
;
}
$exitCode = parent::doRunCommand($command, $input, $output);
return $exitCode;
}
/**
* {@inheritdoc}
*
......@@ -156,145 +141,4 @@ class Application extends BaseApplication
return $inputDefinition;
}
/**
* Load all contexts into Context objects.
*
* @return array
*/
static function getAllContexts($name = '', $application = NULL) {
$contexts = [];
$config = new Config();
$context_files = [];
$finder = new \Symfony\Component\Finder\Finder();
$finder->files()->name('*' . $name . '.yml')->in($config->get('config_path') . '/provision');
foreach ($finder as $file) {
list($context_type, $context_name) = explode('.', $file->getFilename());
$context_files[$context_name] = [
'name' => $context_name,
'type' => $context_type,
'file' => $file,
];
}
foreach ($context_files as $context) {
$class = Context::getClassName($context['type']);
$contexts[$context['name']] = new $class($context['name'], $application);
}
if ($name && isset($contexts[$name])) {
return $contexts[$name];
}
elseif ($name && !isset($contexts[$name])) {
return NULL;
}
else {
return $contexts;
}
}
/**
* Load all server contexts.
*
* @param null $service
* @return mixed
* @throws \Exception
*/
static public function getAllServers($service = NULL) {
$servers = [];
$context_files = self::getAllContexts();
if (empty($context_files)) {
throw new \Exception('No server contexts found. Use `provision save` to create one.');
}
foreach ($context_files as $context) {
if ($context->type == 'server') {
$servers[$context->name] = $context;
}
}
return $servers;
}
/**
* Get a simple array of all contexts, for use in an options list.
* @return array
*/
public function getAllContextsOptions($type = NULL) {
$options = [];
foreach ($this->getAllContexts() as $name => $context) {
if ($type) {
if ($context->type == $type) {
$options[$name] = $context->name;
}
}
else {
$options[$name] = $context->type . ' ' . $context->name;
}
}
return $options;
}
/**
* Load the Aegir context with the specified name.
*
* @param $name
*
* @return \Aegir\Provision\Context
* @throws \Exception
*/
static public function getContext($name, Application $application = NULL) {
if (empty($name)) {
throw new \Exception('Context name must not be empty.');
}
if (empty(Application::getAllContexts($name, $application))) {
throw new \Exception('Context not found with name: ' . $name);
}
return Application::getAllContexts($name, $application);
}
/**
* Get a simple array of all servers, optionally specifying the the service_type to filter by ("http", "db" etc.)
* @param string $service_type
* @return array
*/
public function getServerOptions($service_type = '') {
$servers = [];
foreach ($this->getAllServers() as $server) {
if ($service_type && !empty($server->config['services'][$service_type])) {
$servers[$server->name] = $server->name . ': ' . $server->config['services'][$service_type]['type'];
}
elseif ($service_type == '') {
$servers[$server->name] = $server->name . ': ' . $server->config['services'][$service_type]['type'];
}
}
return $servers;
}
/**
* Check that a context type's service requirements are provided by at least 1 server.
*
* @param $type
* @return array
*/
static function checkServiceRequirements($type) {
$class_name = Context::getClassName($type);
// @var $context Context
$service_requirements = $class_name::serviceRequirements();
$services = [];
foreach ($service_requirements as $service) {
try {
if (empty(Application::getAllServers($service))) {
$services[$service] = 0;
}
else {
$services[$service] = 1;
}
} catch (\Exception $e) {
$services[$service] = 0;
}
}
return $services;
}
}
......@@ -2,7 +2,10 @@
namespace Aegir\Provision;
use Aegir\Provision\Common\ProvisionAwareTrait;
use Drupal\Console\Core\Style\DrupalStyle;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Drupal\Console\Core\Command\Shared\CommandTrait;
......@@ -19,7 +22,8 @@ abstract class Command extends BaseCommand
{
use CommandTrait;
use ProvisionAwareTrait;
use LoggerAwareTrait;
/**
* @var \Symfony\Component\Console\Input\InputInterface
......@@ -50,7 +54,7 @@ abstract class Command extends BaseCommand
* @var string
*/
public $context_name;
/**
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
......@@ -70,7 +74,7 @@ abstract class Command extends BaseCommand
try {
// Load context from context_name argument.
$this->context_name = $this->input->getArgument('context_name');
$this->context = Application::getContext($this->context_name, $this->getApplication());
$this->context = $this->getProvision()->getContext($this->context_name);
}
catch (\Exception $e) {
......@@ -92,7 +96,7 @@ abstract class Command extends BaseCommand
$this->input->setArgument('context_name', $this->context_name);
try {
$this->context = Application::getContext($this->context_name, $this->getApplication());
$this->context = $this->getProvision()->getContext($this->context_name);
}
catch (\Exception $e) {
$this->context = NULL;
......@@ -104,11 +108,11 @@ abstract class Command extends BaseCommand
* Show a list of Contexts to the user for them to choose from.
*/
public function askForContext($question = 'Choose a context') {
if (empty($this->getApplication()->getAllContextsOptions())) {
if (empty($this->getProvision()->getAllContextsOptions())) {
throw new \Exception('No contexts available! use <comment>provision save</comment> to create one.');
}
$this->context_name = $this->io->choice($question, $this->getApplication()->getAllContextsOptions());
$this->context_name = $this->io->choice($question, $this->getProvision()->getAllContextsOptions());
}
/**
......
......@@ -8,6 +8,7 @@ use Aegir\Provision\Context;
use Aegir\Provision\Context\PlatformContext;
use Aegir\Provision\Context\ServerContext;
use Aegir\Provision\Context\SiteContext;
use Aegir\Provision\Provision;
use Aegir\Provision\Service;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\ArrayInput;
......@@ -152,7 +153,7 @@ class SaveCommand extends Command
// Check for context type service requirements.
$exit = FALSE;
$this->io->comment("Checking service requirements for context type {$context_type}...");
$reqs = Application::checkServiceRequirements($context_type);
$reqs = $this->getProvision()->checkServiceRequirements($context_type);
foreach ($reqs as $service => $available) {
if ($available) {
$this->io->successLite("Service $service: Available");
......@@ -169,9 +170,12 @@ class SaveCommand extends Command
}
$properties = $this->askForContextProperties();
$options = $this->askForContextProperties();
$options['name'] = $this->context_name;
$options['type'] = $this->context_type;
$class = Context::getClassName($this->input->getOption('context_type'));
$this->context = new $class($input->getArgument('context_name'), $this->getApplication(), $properties);
$this->context = new $class($input->getArgument('context_name'), $this->getProvision(), $options);
}
// Delete context config.
......@@ -227,7 +231,7 @@ class SaveCommand extends Command
*/
public function askForContext($question = 'Choose a context')
{
$options = $this->getApplication()->getAllContextsOptions();
$options = $this->getProvision()->getAllContextsOptions();
// If there are options, add "new" to the list.
if (count($options)) {
......@@ -305,7 +309,7 @@ class SaveCommand extends Command
// $context_name = $this->io->ask($all_services[$type]);
// }
// $context = Application::getContext($context_name);
// $context = Provision::getContext($context_name);
$this->io->info("Adding required service $type...");
......@@ -348,7 +352,7 @@ class SaveCommand extends Command
$contexts[$property] = $this->input->getOption($property);
try {
$context = Application::getContext($contexts[$property]);
$context = $this->getProvision()->getContext($contexts[$property]);
}
catch (\Exception $e) {
throw new \Exception("Context set by option --{$property} does not exist.");
......@@ -359,7 +363,7 @@ class SaveCommand extends Command
}
}
else {
$contexts[$property] = $this->io->choice("Select $property context", $this->getApplication()->getAllContextsOptions($context_type));
$contexts[$property] = $this->io->choice("Select $property context", $this->getProvision()->getAllContextsOptions($context_type));
}
}
return $contexts;
......
......@@ -219,16 +219,16 @@ class ServicesCommand extends Command
}
// All other context types are associating with servers that provide the service.
else {
if (empty($this->getApplication()->getServerOptions($service))) {
if (empty($this->getProvision()->getServerOptions($service))) {
throw new \Exception("No servers providing $service service were found. Create one with `provision save` or use `provision services` to add to an existing server.");
}
$server = $this->input->getArgument('server')?
$this->input->getArgument('server'):
$this->io->choice('Which server?', $this->getApplication()->getServerOptions($service));
$this->io->choice('Which server?', $this->getProvision()->getServerOptions($service));
// Then ask for all options.
$server_context = $this->getApplication()->getContext($server);
$server_context = $this->getProvision()->getContext($server);
$properties = $this->askForServiceProperties($service);
$this->io->info("Using $service service from server $server...");
......
......@@ -43,6 +43,7 @@ class StatusCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getProvision();
if ($input->getArgument('context_name')) {
$rows = [['Configuration File', $this->context->config_path]];
......@@ -59,7 +60,7 @@ class StatusCommand extends Command
else {
$headers = ['Provision CLI Configuration'];
$rows = [];
$config = $this->getApplication()->getConfig()->toArray();
$config = $this->getProvision()->getConfig()->toArray();
unset($config['options']);
foreach ($config as $key => $value) {
$rows[] = [$key, $value];
......@@ -68,14 +69,14 @@ class StatusCommand extends Command
// Lookup all contexts
$rows = [];
foreach ($this->getApplication()->getAllContexts() as $context) {
foreach ($this->getProvision()->getAllContexts() as $context) {
$rows[] = [$context->name, $context->type];
}
$headers = ['Contexts'];
$this->io->table($headers, $rows);
// Offer to output a context status.
$options = $this->getApplication()->getAllContextsOptions();
$options = $this->getProvision()->getAllContextsOptions();
$options['none'] = 'none';
$context = $this->io->choiceNoList('Get status for', $options, 'none');
......
......@@ -60,6 +60,11 @@ class VerifyCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (empty($this->context)) {
throw new \Exception("You must specify a context to verify.");
}
$this->io->title(strtr("Verify %type: %name", [
'%name' => $this->context_name,
'%type' => $this->context->type,
......@@ -75,8 +80,7 @@ class VerifyCommand extends Command
}
*/
$message = $this->context->verify();
$message = $this->context->verifyCommand();
$this->io->comment($message);
}
}
<?php
namespace Aegir\Provision\Common;
use Aegir\Provision\Context;
trait ContextAwareTrait
{
/**
* @var Context
*/
protected $context = NULL;
/**
* @param Context $context
*
* @return $this
*/
public function setContext(Context $context = NULL)
{
$this->context = $context;
return $this;
}
/**
* @return Context
*/
public function getContext()
{
return $this->context;
}
}
<?php
namespace Aegir\Provision\Common;
use Aegir\Provision\Context;
use Symfony\Component\Process\Process;
use Twig\Node\Expression\Unary\NegUnary;
trait ProcessAwareTrait
{
/**
* @var Process
*/
protected $process = NULL;
// /**
// * @var string
// */
// protected $command;
/**
* @param Process $process
*
* @return $this
*/
protected function setProcess(Process $process = NULL)
{
$this->process = $process;
return $this;
}
/**
* @return Process
*/
public function getProcess()
{
if (is_null($this->process)) {
$this->process = new Process($this->command);
}
return $this->process;
}
/**
* @param $command
*
* @return $this
*/
public function setCommand($command) {
$this->command = $command;
return $this;
}
/**
* @return string
*/
public function getCommand() {
return $this->command;
}
public function execute() {
$this->process->run();
}
}
<?php
namespace Aegir\Provision\Common;
use Aegir\Provision\Provision;
trait ProvisionAwareTrait
{
/**
* @var Provision
*/
protected $provision = NULL;
/**
* @param Provision $provision
*
* @return $this
*/
public function setProvision(Provision $provision = NULL)
{
$this->provision = $provision;
return $this;
}
/**
* @return Provision
*/
public function getProvision()
{
if (is_null($this->provision)) {
return Provision::getProvision();
}
return $this->provision;
}
}
......@@ -2,7 +2,7 @@
namespace Aegir\Provision\ConfigDefinition;
use Aegir\Provision\Application;
use Aegir\Provision\Common\ProvisionAwareTrait;
use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
......@@ -28,6 +28,8 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
*/
class ContextNodeDefinition extends ScalarNodeDefinition
{
use ProvisionAwareTrait;
protected function createNode()
{
/**
......@@ -50,12 +52,13 @@ class ContextNodeDefinition extends ScalarNodeDefinition
*/
public function validateContext($value)
{
$this->setProvision($this->getNode()->getAttribute('provision'));
// No need to do anything else.
// If there is no context named $value, getContext() throws an exception for us.
Application::getContext($value);
// If context_type is specified, Validate that the desired context is the right type.
if ($this->getNode()->getAttribute('context_type') && Application::getContext($value)->type != $this->getNode()->getAttribute('context_type')) {
if ($this->getNode()->getAttribute('context_type') && $this->getProvision()->getContext($value)->type != $this->getNode()->getAttribute('context_type')) {
throw new InvalidConfigurationException(strtr('The context specified for !name must be type !type.', [
'!name' => $this->name,
'!type' => $this->getNode()->getAttribute('context_type'),
......@@ -69,8 +72,8 @@ class ContextNodeDefinition extends ScalarNodeDefinition
$this->getNode()->getAttribute('service_requirement'):
$path[2]
;
Application::getContext($value)->getService($service);
$this->getProvision()->getContext($value)->getService($service);
}
return $value;
}
......
......@@ -2,66 +2,48 @@
namespace Aegir\Provision\Console;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Console\Exception\InvalidOptionException;
/**
* Class Config.
* Class Config
* @package Aegir\Provision\Console
*
* Many thanks to pantheon-systems/terminus. Inspired by DefaultConfig
*/
class Config implements ConfigurationInterface
class Config extends ProvisionConfig
{
/**
* Configuration values array.
*
* @var array
*/
protected $config = [];
/**
* Path to config YML file.
*
* @var string
*/
private $config_path = '';
/**
* Filename of config YML file.
*
* @var string
*/
private $config_filename = '.provision.yml';
const CONFIG_FILENAME = '.provision.yml';
/**
* {@inheritdoc}
* DefaultsConfig constructor.
*/
public function __construct()
{
$this->config_path = $this->getHomeDir().'/'.$this->config_filename;
try {
$processor = new Processor();
$configs = func_get_args();
if (file_exists($this->config_path)) {
$configs[] = Yaml::parse(file_get_contents($this->config_path));
}
$this->config = $processor->processConfiguration($this, $configs);
} catch (\Exception $e) {
throw new Exception(
'There is an error with your configuration: '.$e->getMessage()
);
}
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');
$file = $this->get('user_home') . DIRECTORY_SEPARATOR . Config::CONFIG_FILENAME;
$this->set('console_config_file', $file);
$this->extend(new YamlConfig($this->get('user_home') . DIRECTORY_SEPARATOR . Config::CONFIG_FILENAME));
$this->extend(new DotEnvConfig(getcwd()));
$this->extend(new EnvConfig());
$this->validateConfig();
}
/**
* Check configuration values against the current system.
*
......@@ -70,204 +52,108 @@ class Config implements ConfigurationInterface
protected function validateConfig() {
// Check that aegir_root is writable.
// @TODO: Create some kind of Setup functionality.
if (!is_writable($this->config['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}."
if (!is_writable($this->get('aegir_root'))) {
throw new InvalidOptionException(
"The folder set to 'aegir_root' ({$this->get('aegir_root')}) is not writable. Fix this or change the aegir_root value in the file {$this->get('console_config_file')}"
);
}
// Check that config_path exists and is writable.
if (!file_exists($this->config['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}."
// If config_path does not exist and we cannot create it...
if (!file_exists($this->get('config_path')) && !mkdir($this->get('config_path'))) {
throw new InvalidOptionException(
"The folder set to 'config_path' ({$this->get('config_path')}) does not exist, and cannot be created. Create it manually or change the 'config_path' value in the file {$this->get('console_config_file')}."
);
}
elseif (!is_writable($this->config['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 (!is_writable($this->get('config_path'))) {
throw new InvalidOptionException(
"The folder set to 'config_path' ({$this->get('config_path')}) is not writable. Fix this or change the config_path value in the file {$this->get('console_config_file')}."
);
}
elseif (!file_exists($this->config['config_path'] . '/provision')) {
mkdir($this->config['config_path'] . '/provision');
elseif (!file_exists($this->get('config_path') . '/provision')) {
mkdir($this->get('config_path') . '/provision');
}
// Ensure that script_user is the user.
$real_script_user = $this->getScriptUser();
if ($this->config['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['script_user']} or change the script_user value in the file {$this->config_path}."
if ($this->get('script_user') != $real_script_user) {
throw new InvalidOptionException(
"The user set as 'script_user' ({$this->get('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->get('console_config_file')}}."
);
}
}
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('aegir');
$root_node
->children()
->scalarNode('aegir_root')
->defaultValue(Config::getHomeDir())
->end()
->scalarNode('script_user')
->defaultValue(Config::getScriptUser())
->end()
->scalarNode('config_path')
->defaultValue(Config::getHomeDir() . '/config')
->end()
->end();;
return $tree_builder;
}
/**
* Get a config param value.
*
* @param string $key
* Key of the param to get.
*
* @return mixed|null
* Value of the config param, or NULL if not present.
*/
public function get($key, $name = null)
{
if ($name) {
return array_key_exists(
$name,
$this->config[$key]
) ? $this->config[$key][$name] : null;
} else {
return $this->has($key) ? $this->config[$key] : null;
}
}
/**
* Check if config param is present.
*
* @param string $key
* Key of the param to check.
*
* @return bool
* TRUE if key exists.
*/
public function has($key)
{
return array_key_exists($key, $this->config);
}
/**
* Set a config param value.
*
* @param string $key
* Key of the param to get.
* @param mixed $val
* Value of the param to set.
* Get the name of the source for this configuration object.
*
* @return bool
* @return string
*/
public function set($key, $val)
public function getSourceName()
{
return $this->config[$key] = $val;
return 'Default';
}
/**
* Get all config values.
* Returns location of PHP with which to run Terminus
*
* @return array
* All config galues.
* @return string
*/
public function all()
protected function getPhpBinary()
{
return $this->config;
return defined('PHP_BINARY') ? PHP_BINARY : 'php';
}
/**
* Add a config param value to a config array.
*
* @param string $key
* Key of the group to set to.
* @param string|array $names
* Name of the new object to set.
* @param mixed $val
* Value of the new object to set.
* Finds and returns the root directory of Provision
*
* @return bool
* @param string $current_dir Directory to start searching at
* @return string
* @throws \Exception
*/
public function add($key, $names, $val)
protected function getProvisionRoot($current_dir = null)
{
if (is_array($names)) {
$array_piece = &$this->config[$key];
foreach ($names as $name_key) {
$array_piece = &$array_piece[$name_key];
}
return $array_piece = $val;
} else {
return $this->config[$key][$names] = $val;
if (is_null($current_dir)) {
$current_dir = dirname(__DIR__);
}
}
/**
* Remove a config param from a config array.
*
* @param $key
* @param $name
*
* @return bool
*/
public function remove($key, $name)
{
if (isset($this->config[$key][$name])) {
unset($this->config[$key][$name]);
return true;
} else {
return false;
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;
}
/**
* Saves the config class to file.
* Finds and returns the name of the script running Terminus functions
*
* @return bool
* @return string
*/
public function save()
protected function getProvisionScript()
{
// Create config folder if it does not exist.
$fs = new Filesystem();
$dumper = new Dumper();
try {
$fs->dumpFile($this->config_path, $dumper->dump($this->config, 10));
return true;
} catch (IOExceptionInterface $e) {
return false;
}
$debug = debug_backtrace();
$script_location = array_pop($debug);
$script_name = str_replace(
$this->getProvisionRoot() . DIRECTORY_SEPARATOR,
'',
$script_location['file']
);
return $script_name;
}
/**
* Determine the user running provision.
*/
static function getScriptUser() {
$real_script_user = posix_getpwuid(posix_geteuid());
return $real_script_user['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
*/
static function getHomeDir()
protected function getHomeDir()
{
$home = getenv('HOME');
if (!$home) {
......@@ -279,14 +165,14 @@ class Config implements ConfigurationInterface
$home = getenv('HOMEPATH');
}
}
return $home;
}
/**
* Determine the user running provision.
*/
public function getConfigPath() {
return $this->config_path;
protected function getScriptUser() {
$real_script_user = posix_getpwuid(posix_geteuid());
return $real_script_user['name'];
}
}
<?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'];
}
}
......@@ -6,12 +6,15 @@
namespace Aegir\Provision;
use Aegir\Provision\Common\ProvisionAwareTrait;
use Aegir\Provision\Console\Config;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Drupal\Console\Core\Style\DrupalStyle;
use Psr\Log\LoggerInterface;
use Robo\Common\BuilderAwareTrait;
use Robo\Contract\BuilderAwareInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Yaml\Dumper;
......@@ -24,9 +27,12 @@ use Symfony\Component\Yaml\Yaml;
*
* @package Aegir\Provision
*/
class Context
class Context implements BuilderAwareInterface
{
use BuilderAwareTrait;
use ProvisionAwareTrait;
/**
* @var string
* Name for saving aliases and referencing.
......@@ -60,27 +66,23 @@ class Context
* init(), set defaults with setProperty().
*/
protected $properties = [];
/**
* @var \Aegir\Provision\Application;
*/
public $application;
/**
* @var LoggerInterface
*/
public $logger;
/**
* Context constructor.
*
* @param $name
* @param array $options
*/
function __construct($name, Application $application = NULL, $options = [])
function __construct(
$name,
Provision $provision,
$options = [])
{
$this->name = $name;
$this->application = $application;
$this->setProvision($provision);
$this->setBuilder($this->getProvision()->getBuilder());
$this->loadContextConfig($options);
$this->prepareServices();
}
......@@ -94,8 +96,8 @@ class Context
*/
private function loadContextConfig($options = []) {
if ($this->application) {
$this->config_path = $this->application->getConfig()->get('config_path') . '/provision/' . $this->type . '.' . $this->name . '.yml';
if ($this->getProvision()) {
$this->config_path = $this->getProvision()->getConfig()->get('config_path') . '/provision/' . $this->type . '.' . $this->name . '.yml';
}
else {
$config = new Config();
......@@ -115,14 +117,16 @@ class Context
$this->properties[$option] = $options[$option];
}
}
$this->properties['type'] = $this->type;
$this->properties['name'] = $this->name;
$configs[] = $this->properties;
$this->properties['context_type'] = $this->type;
$this->config = $processor->processConfiguration($this, $configs);
} catch (\Exception $e) {
throw new \Exception(
throw new InvalidOptionException(
strtr("There is an error with the configuration for !type '!name'. Check the file !file and try again. \n \nError: !message", [
'!type' => $this->type,
'!name' => $this->name,
......@@ -269,6 +273,11 @@ class Context
->children()
->scalarNode('name')
->defaultValue($this->name)
->isRequired()
->end()
->scalarNode('type')
->defaultValue($this->type)
->isRequired()
->end()
->end();
......@@ -293,6 +302,7 @@ class Context
->node($property, 'context')
->isRequired()
->attribute('context_type', $type)
->attribute('provision', $this->getProvision())
->end()
->end();
}
......@@ -323,7 +333,7 @@ class Context
// If type is empty, it's because it's in the ServerContext
if (empty($info['type'])) {
$server = Application::getContext($info['server']);
$server = $this->getProvision()->getContext($info['server']);
$service_type = ucfirst($server->getService($service)->type);
}
else {
......@@ -466,32 +476,33 @@ class Context
*
* If this context is a Service Subscriber, the provider service will be verified first.
*/
public function verify() {
public function verifyCommand() {
$return_codes = [];
// Run verify method on all services.
foreach ($this->getServices() as $type => $service) {
$friendlyName = $service->getFriendlyName();
if ($this->isProvider()) {
$this->application->io->section("Verify service: {$friendlyName}");
foreach ($service->verify() as $type => $verify_success) {
$return_codes[] = $verify_success? 0: 1;
}
}
else {
$this->application->io->section("Verify service: {$friendlyName} on {$service->provider->name}");
// First verify the service provider.
foreach ($service->verifyProvider() as $verify_part => $verify_success) {
$return_codes[] = $verify_success? 0: 1;
}
// Then run "verify" on the subscriptions.
foreach ($this->getSubscription($type)->verify() as $type => $verify_success) {
$return_codes[] = $verify_success? 0: 1;
}
}
$return_codes[] = $service->verify() ? 0 : 1;
}
//
// if ($this->isProvider()) {
// $this->getProvision()->io()->section("Verify service: {$friendlyName}");
// foreach ($service->verify() as $type => $verify_success) {
// $return_codes[] = $verify_success? 0: 1;
// }
// }
// else {
// $this->getProvision()->io()->section("Verify service: {$friendlyName} on {$service->provider->name}");
//
// // First verify the service provider.
// foreach ($service->verifyProvider() as $verify_part => $verify_success) {
// $return_codes[] = $verify_success? 0: 1;
// }
//
// // Then run "verify" on the subscriptions.
// foreach ($this->getSubscription($type)->verify() as $type => $verify_success) {
// $return_codes[] = $verify_success? 0: 1;
// }
// }
// }
// If any service verify failed, exit with a non-zero code.
if (count(array_filter($return_codes))) {
......
......@@ -4,6 +4,7 @@ namespace Aegir\Provision\Context;
use Aegir\Provision\Application;
use Aegir\Provision\ContextSubscriber;
use Aegir\Provision\Provision;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
......@@ -30,12 +31,15 @@ class PlatformContext extends ContextSubscriber implements ConfigurationInterfac
* PlatformContext constructor.
*
* @param $name
* @param Application $application
* @param Provision $provision
* @param array $options
*/
function __construct($name, Application $application = NULL, array $options = [])
{
parent::__construct($name, $application, $options);
function __construct(
$name,
Provision $provision = NULL,
$options = []
) {
parent::__construct($name, $provision, $options);
// Load "web_server" context.
// There is no need to validate for $this->properties['web_server'] because the config system does that.
......@@ -71,8 +75,15 @@ class PlatformContext extends ContextSubscriber implements ConfigurationInterfac
*/
public function verify()
{
$this->application->io->customLite($this->getProperty('root'), 'Root: ', 'info');
$this->application->io->customLite($this->config_path, 'Configuration File: ', 'info');
$this->getProvision()->io()->customLite($this->getProperty('root'), 'Root: ', 'info');
$this->getProvision()->io()->customLite($this->config_path, 'Configuration File: ', 'info');
// This is how you can use Robo Tasks in a platform verification call.
// The "silent" method actually works here.
// It only partially works in Service::restartServices()!
$this->getBuilder()->taskExec('env')
->silent(!$this->getProvision()->io()->isVerbose())
->run();
return parent::verify();
}
......
......@@ -3,7 +3,9 @@
namespace Aegir\Provision\Context;
use Aegir\Provision\Application;
use Aegir\Provision\Context;
use Aegir\Provision\ContextSubscriber;
use Aegir\Provision\Provision;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
......@@ -34,17 +36,20 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
* @param Application $application
* @param array $options
*/
function __construct($name, Application $application = NULL, array $options = [])
{
parent::__construct($name, $application, $options);
function __construct(
$name,
Provision $provision = NULL,
$options = []
) {
parent::__construct($name, $provision, $options);
// Load "web_server" and "platform" contexts.
// There is no need to check if the property exists because the config system does that.
// $this->db_server = $application->getContext($this->properties['db_server']);
$this->platform = Application::getContext($this->properties['platform'], $application);
// Load platform context... @TODO: Automatically do this for required contexts?
$this->platform = $this->getProvision()->getContext($this->properties['platform']);
// Add platform http service subscription.
$this->serviceSubscriptions['http'] = $this->platform->getSubscription('http');
$this->serviceSubscriptions['http']->context = $this;
......@@ -82,9 +87,9 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
*/
public function verify()
{
$this->application->io->customLite($this->getProperty('uri'), 'Site URL: ', 'info');
$this->application->io->customLite($this->platform->getProperty('root'), 'Root: ', 'info');
$this->application->io->customLite($this->config_path, 'Configuration File: ', 'info');
$this->getProvision()->io()->customLite($this->getProperty('uri'), 'Site URL: ', 'info');
$this->getProvision()->io()->customLite($this->platform->getProperty('root'), 'Root: ', 'info');
$this->getProvision()->io()->customLite($this->config_path, 'Configuration File: ', 'info');
return parent::verify();
}
......
......@@ -93,6 +93,7 @@ class ContextSubscriber extends Context
->node('server', 'context')
->isRequired()
->attribute('context_type', 'server')
->attribute('provision', $this->getProvision())
->end()
->append($this->addServiceProperties('service_subscriptions'))
->end()
......
......@@ -3,33 +3,54 @@
namespace Aegir\Provision;
use Aegir\Provision\Console\Config;
use Aegir\Provision\Commands\ExampleCommands;
use Consolidation\Config\Loader\ConfigProcessor;
use Aegir\Provision\Console\ConsoleOutput;
use Aegir\Provision\Robo\ProvisionCollectionBuilder;
use Aegir\Provision\Robo\ProvisionExecutor;
use Aegir\Provision\Robo\ProvisionTasks;
use Drupal\Console\Core\Style\DrupalStyle;
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 Psr\Log\LogLevel;
use Robo\Common\BuilderAwareTrait;
use Robo\Common\ConfigAwareTrait;
use Robo\Config\Config;
use Robo\Common\IO;
use Robo\Contract\BuilderAwareInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Contract\IOAwareInterface;
use Robo\Robo;
use Robo\Runner as RoboRunner;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
class Provision implements ConfigAwareInterface, ContainerAwareInterface, LoggerAwareInterface {
/**
* Class Provision
*
* Uses BuilderAwareTrait to allow access to Robo Tasks:
*
* $this->getBuilder()->taskExec('ls -la')
* ->run()
*
* @package Aegir\Provision
*/
class Provision implements ConfigAwareInterface, ContainerAwareInterface, LoggerAwareInterface, IOAwareInterface, BuilderAwareInterface {
const APPLICATION_NAME = 'Aegir Provision';
const VERSION = '4.x-dev';
const REPOSITORY = 'aegir-project/provision';
use BuilderAwareTrait;
use ConfigAwareTrait;
use ContainerAwareTrait;
use LoggerAwareTrait;
use IO;
/**
* @var \Robo\Runner
......@@ -41,30 +62,50 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
*/
private $commands = [];
/**
* @var \Aegir\Provision\Context[]
*/
private $contexts = [];
/**
* @var array[]
*/
private $context_files = [];
/**
* Provision constructor.
*
* @param \Aegir\Provision\Console\Config $config
* @param \Symfony\Component\Console\Input\InputInterface|null $input
* @param \Symfony\Component\Console\Output\OutputInterface|null $output
*/
public function __construct(
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());
$logger = new ConsoleLogger($output);
$this->setConfig($config);
$this
->setConfig($config)
;
// Create Application.
$application = new \Aegir\Provision\Application(self::APPLICATION_NAME, self::VERSION, $this);
$application->console = $output;
// $application->setConfig($consoleConfig);
$application = new Application(self::APPLICATION_NAME, self::VERSION);
$application
->setProvision($this)
->setLogger($logger)
;
$application->configureIO($input, $output);
$this->setInput($input);
$this->setOutput($output);
// Create and configure container.
$container = Robo::createDefaultContainer($input, $output, $application, $config);
$this->setContainer($container);
$this->configureContainer($container);
// Instantiate Robo Runner.
$this->runner = new RoboRunner([
ExampleCommands::class
......@@ -72,16 +113,59 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
$this->runner->setContainer($container);
$this->runner->setSelfUpdateRepository(self::REPOSITORY);
$this->setBuilder($container->get('builder'));
$this->setLogger($container->get('logger'));
$this->loadAllContexts();
}
/**
* Lookup all context yml files and load into Context classes.
*/
private function loadAllContexts()
{
$folder = $this->getConfig()->get('config_path') . '/provision';
$finder = new \Symfony\Component\Finder\Finder();
$finder->files()->name("*.yml")->in($folder);
foreach ($finder as $file) {
$context_type = substr($file->getFilename(), 0, strpos($file->getFilename(), '.'));
$context_name = substr($file->getFilename(), strpos($file->getFilename(), '.') + 1, strlen($file->getFilename()) - strlen($context_type) - 5);
$this->context_files[$context_name] = [
'name' => $context_name,
'type' => $context_type,
'file' => $file,
];
}
// Load Context classes from files metadata.
foreach ($this->context_files as $context) {
$class = Context::getClassName($context['type']);
$this->contexts[$context['name']] = new $class($context['name'], $this);
}
}
/**
* Loads a single context from file into $this->contexts[$name].
*
* Used to load dependant contexts that might not be instantiated yet.
*
* @param $name
*/
public function loadContext($name) {
$class = Context::getClassName($this->context_files[$name]['type']);
$this->contexts[$this->context_files[$name]['name']] = new $class($this->context_files[$name]['name'], $this);
}
public function run(InputInterface $input, OutputInterface $output) {
$status_code = $this->runner->run($input, $output);
return $status_code;
}
/**
* Register the necessary classes for BLT.
* Register the necessary classes for Provision.
*/
public function configureContainer(Container $container) {
......@@ -91,9 +175,208 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
// in the container. "collectionBuilder" used for the actual command that
// was executed, and "builder" to be used with non-command classes.
$tasks = new ProvisionTasks();
$builder = new CollectionBuilder($tasks);
$builder = new ProvisionCollectionBuilder($tasks);
$tasks->setBuilder($builder);
$container->add('builder', $builder);
$container->add('executor', ProvisionExecutor::class)
->withArgument('builder');
}
/**
* Temporary helper to allow public access to output.
*
* @return \Symfony\Component\Console\Output\OutputInterface
*/
public function getOutput()
{
return $this->output();
}
/**
* Temporary helper to allow public access to input.
*
* @return \Symfony\Component\Console\Input\InputInterface
*/
public function getInput()
{
return $this->input();
}
/**
* Gets Logger object.
* Returns the currently active Logger instance.
*
* @return \Psr\Log\LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* Provide access to DrupalStyle object.
*
* @return \Drupal\Console\Core\Style\DrupalStyle
*/
public function io()
{
if (!$this->io) {
$this->io = new DrupalStyle($this->input(), $this->output());
}
return $this->io;
}
/**
* Get a new Provision
* @return \Aegir\Provision\Provision
*/
static function getProvision() {
$input = new ArgvInput();
$output = new ConsoleOutput();
$config = new Config();
return new Provision($config, $input, $output);
}
/**
* Return all available contexts.
*
* @return array|Context
*/
public function getAllContexts($name = '') {
if ($name && isset($this->contexts[$name])) {
return $this->contexts[$name];
}
elseif ($name && !isset($this->contexts[$name])) {
return NULL;
}
else {
return $this->contexts;
}
}
/**
* Load all server contexts.
*
* @param null $service
* @return mixed
* @throws \Exception
*/
protected function getAllServers($service = NULL) {
$servers = [];
$context_files = $this->getAllContexts();
if (empty($context_files)) {
throw new \Exception('No server contexts found. Use `provision save` to create one.');
}
foreach ($context_files as $context) {
if ($context->type == 'server') {
$servers[$context->name] = $context;
}
}
return $servers;
}
/**
* Get a simple array of all contexts, for use in an options list.
* @return array
*/
public function getAllContextsOptions($type = NULL) {
$options = [];
foreach ($this->getAllContexts() as $name => $context) {
if ($type) {
if ($context->type == $type) {
$options[$name] = $context->name;
}
}
else {
$options[$name] = $context->type . ' ' . $context->name;
}
}
return $options;
}
/**
* Load the Aegir context with the specified name.
*
* @param $name
*
* @return array|\Aegir\Provision\Context
* @throws \Exception
*/
public function getContext($name) {
// Check if $name is empty.
if (empty($name)) {
throw new \Exception('Context name must not be empty.');
}
// If context exists but hasn't been loaded, load it.
if (empty($this->contexts[$name]) && !empty($this->context_files[$name])) {
$this->loadContext($name);
}
// Check if context still isn't found.
if (empty($this->contexts[$name])) {
throw new \Exception('Context not found with name: ' . $name);
}
return $this->contexts[$name];
}
/**
* Look for a context file being present. This is available before Context
* objects are bootstrapped.
*/
public function getContextFile($name) {
if (empty($name)) {
throw new \Exception('Context name must not be empty.');
}
if (empty($this->context_files[$name])) {
throw new \Exception('Context not found with name: ' . $name);
}
return$this->context_files[$name];
}
/**
* Get a simple array of all servers, optionally specifying the the service_type to filter by ("http", "db" etc.)
* @param string $service_type
* @return array
*/
public function getServerOptions($service_type = '') {
$servers = [];
foreach ($this->getAllServers() as $server) {
if ($service_type && !empty($server->config['services'][$service_type])) {
$servers[$server->name] = $server->name . ': ' . $server->config['services'][$service_type]['type'];
}
elseif ($service_type == '') {
$servers[$server->name] = $server->name . ': ' . $server->config['services'][$service_type]['type'];
}
}
return $servers;
}
/**
* Check that a context type's service requirements are provided by at least 1 server.
*
* @param $type
* @return array
*/
public function checkServiceRequirements($type) {
$class_name = Context::getClassName($type);
// @var $context Context
$service_requirements = $class_name::serviceRequirements();
$services = [];
foreach ($service_requirements as $service) {
try {
if (empty($this->getAllServers($service))) {
$services[$service] = 0;
}
else {
$services[$service] = 1;
}
} catch (\Exception $e) {
$services[$service] = 0;
}
}
return $services;
}
}
<?php
namespace Aegir\Provision\Robo;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Robo\Collection\CollectionBuilder;
use Robo\Collection\NestedCollectionInterface;
use Robo\Common\CommandArguments;
use Robo\Common\ExecCommand;
use Robo\Common\ExecTrait;
use Robo\Common\OutputAdapter;
use Robo\Common\TaskIO;
use Robo\Result;
use Robo\ResultData;
use Symfony\Component\Process\Process;
class ProvisionCollectionBuilder extends CollectionBuilder {
/**
* @var string|\Robo\Contract\CommandInterface
*/
protected $command;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $currentTask;
}
\ No newline at end of file
<?php
namespace Aegir\Provision\Robo;
use GuzzleHttp\Client;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Robo\Collection\CollectionBuilder;
use Robo\Common\ConfigAwareTrait;
use Robo\Common\IO;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\Robo;
use Robo\Contract\ConfigAwareInterface;
use Robo\Contract\IOAwareInterface;
use Symfony\Component\Process\Process;
/**
* A class for executing commands.
*
* This allows non-Robo-command classes to execute commands easily.
*/
class ProvisionExecutor implements ConfigAwareInterface, IOAwareInterface, LoggerAwareInterface {
use ConfigAwareTrait;
use IO;
use LoggerAwareTrait;
/**
* A copy of the Robo builder.
*
* @var \Aegir\Provision\Robo\ProvisionTasks
*/
protected $builder;
/**
* Executor constructor.
*
* @param \Robo\Collection\CollectionBuilder $builder
* This is a copy of the collection builder, required for calling various
* Robo tasks from non-command files.
*/
public function __construct(CollectionBuilder $builder) {
$this->builder = $builder;
}
/**
* Returns $this->builder.
*
* @return \Aegir\Provision\Robo\ProvisionTasks
* The builder.
*/
public function getBuilder() {
return $this->builder;
}
/**
* Wrapper for taskExec().
*
* @param string $command
* The command to execute.
*
* @return \Robo\Task\Base\Exec
* The task. You must call run() on this to execute it!
*/
public function taskExec($command) {
return $this->builder->taskExec($command);
}
// /**
// * Executes a drush command.
// *
// * @param string $command
// * The command to execute, without "drush" prefix.
// *
// * @return \Robo\Common\ProcessExecutor
// * The unexecuted process.
// */
// public function drush($command) {
// // @todo Set to silent if verbosity is less than very verbose.
// $bin = $this->getConfigValue('composer.bin');
// /** @var \Robo\Common\ProcessExecutor $process_executor */
// $drush_alias = $this->getConfigValue('drush.alias');
// if (!empty($drush_alias)) {
// $drush_alias = "@$drush_alias";
// }
//
// $command_string = "'$bin/drush' $drush_alias $command";
//
// if ($this->input()->hasOption('yes') && $this->input()->getOption('yes')) {
// $command_string .= ' -y';
// }
//
// $process_executor = Robo::process(new Process($command_string));
//
// return $process_executor->dir($this->getConfigValue('docroot'))
// ->interactive(FALSE)
// ->printOutput(TRUE)
// ->printMetadata(TRUE)
// ->setVerbosityThreshold(VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE);
// }
/**
* Executes a command.
*
* @param string $command
* The command.
*
* @return \Robo\Common\ProcessExecutor
* The unexecuted command.
*/
public function execute($command) {
/** @var \Robo\Common\ProcessExecutor $process_executor */
$process_executor = Robo::process(new Process($command));
return $process_executor->dir($this->getConfigValue('repo.root'))
->printOutput(FALSE)
->printMetadata(FALSE)
->interactive(FALSE);
}
/**
* Kills all system processes that are using a particular port.
*
* @param string $port
* The port number.
*/
public function killProcessByPort($port) {
$this->logger->info("Killing all processes on port '$port'...");
// This is allowed to fail.
// @todo Replace with standardized call to Symfony Process.
exec("command -v lsof && lsof -ti tcp:$port | xargs kill l 2>&1");
exec("pkill -f $port 2>&1");
}
/**
* Kills all system processes containing a particular string.
*
* @param string $name
* The name of the process.
*/
public function killProcessByName($name) {
$this->logger->info("Killing all processing containing string '$name'...");
// This is allowed to fail.
// @todo Replace with standardized call to Symfony Process.
exec("ps aux | grep -i $name | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1");
// exec("ps aux | awk '/$name/ {print $2}' 2>&1 | xargs kill -9");.
}
/**
* Waits until a given URL responds with a non-50x response.
*
* This does have a maximum timeout, defined in wait().
*
* @param string $url
* The URL to wait for.
*/
public function waitForUrlAvailable($url) {
$this->wait([$this, 'checkUrl'], [$url], "Waiting for response from $url...");
}
/**
* Waits until a given callable returns TRUE.
*
* This does have a maximum timeout.
*
* @param callable $callable
* The method/function to wait for a TRUE response from.
* @param array $args
* Arguments to pass to $callable.
* @param string $message
* The message to display when this function is called.
*
* @return bool
* TRUE if callable returns TRUE.
*
* @throws \Exception
*/
public function wait(callable $callable, array $args, $message = '') {
$maxWait = 60 * 1000;
$checkEvery = 1 * 1000;
$start = microtime(TRUE) * 1000;
$end = $start + $maxWait;
if (!$message) {
$method_name = is_array($callable) ? $callable[1] : $callable;
$message = "Waiting for $method_name() to return true.";
}
// For some reason we can't reuse $start here.
while (microtime(TRUE) * 1000 < $end) {
$this->logger->info($message);
try {
if (call_user_func_array($callable, $args)) {
return TRUE;
}
}
catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
usleep($checkEvery * 1000);
}
throw new BltException("Timed out.");
}
/**
* Checks a URL for a non-50x response.
*
* @param string $url
* The URL to check.
*
* @return bool
* TRUE if URL responded with a non-50x response.
*/
public function checkUrl($url) {
try {
$client = new Client();
$res = $client->request('GET', $url, [
'connection_timeout' => 2,
'timeout' => 2,
'exceptions' => FALSE,
]);
if ($res->getStatusCode() && substr($res->getStatusCode(), 0, 1) != '5') {
return TRUE;
}
else {
return FALSE;
}
}
catch (\Exception $e) {
}
return FALSE;
}
}