...
 
Commits (11)
......@@ -74,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 = Provision::getContext($this->context_name, $this->getProvision());
$this->context = $this->getProvision()->getContext($this->context_name);
}
catch (\Exception $e) {
......@@ -96,7 +96,7 @@ abstract class Command extends BaseCommand
$this->input->setArgument('context_name', $this->context_name);
try {
$this->context = Provision::getContext($this->context_name, $this->getProvision());
$this->context = $this->getProvision()->getContext($this->context_name);
}
catch (\Exception $e) {
$this->context = NULL;
......
......@@ -153,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 = Provision::checkServiceRequirements($context_type);
$reqs = $this->getProvision()->checkServiceRequirements($context_type);
foreach ($reqs as $service => $available) {
if ($available) {
$this->io->successLite("Service $service: Available");
......@@ -170,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->getProvision(), $properties);
$this->context = new $class($input->getArgument('context_name'), $this->getProvision(), $options);
}
// Delete context config.
......@@ -349,7 +352,7 @@ class SaveCommand extends Command
$contexts[$property] = $this->input->getOption($property);
try {
$context = Provision::getContext($contexts[$property]);
$context = $this->getProvision()->getContext($contexts[$property]);
}
catch (\Exception $e) {
throw new \Exception("Context set by option --{$property} does not exist.");
......
......@@ -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]];
......
......@@ -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,
......
<?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();
}
}
......@@ -2,8 +2,7 @@
namespace Aegir\Provision\ConfigDefinition;
use Aegir\Provision\Application;
use Aegir\Provision\Provision;
use Aegir\Provision\Common\ProvisionAwareTrait;
use Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
......@@ -29,6 +28,8 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
*/
class ContextNodeDefinition extends ScalarNodeDefinition
{
use ProvisionAwareTrait;
protected function createNode()
{
/**
......@@ -51,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.
Provision::getContext($value);
// If context_type is specified, Validate that the desired context is the right type.
if ($this->getNode()->getAttribute('context_type') && Provision::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'),
......@@ -70,8 +72,8 @@ class ContextNodeDefinition extends ScalarNodeDefinition
$this->getNode()->getAttribute('service_requirement'):
$path[2]
;
Provision::getContext($value)->getService($service);
$this->getProvision()->getContext($value)->getService($service);
}
return $value;
}
......
......@@ -14,6 +14,7 @@ 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;
......@@ -74,17 +75,16 @@ class Context implements BuilderAwareInterface
*/
function __construct(
$name,
Provision $provision = NULL,
Provision $provision,
$options = [])
{
$this->name = $name;
$this->setProvision($provision);
$this->setBuilder($this->getProvision()->getBuilder());
$this->loadContextConfig($options);
$this->prepareServices();
if ($provision) {
$this->setProvision($provision);
$this->setBuilder($this->getProvision()->getBuilder());
}
}
/**
......@@ -117,14 +117,16 @@ class Context implements BuilderAwareInterface
$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,
......@@ -271,6 +273,11 @@ class Context implements BuilderAwareInterface
->children()
->scalarNode('name')
->defaultValue($this->name)
->isRequired()
->end()
->scalarNode('type')
->defaultValue($this->type)
->isRequired()
->end()
->end();
......@@ -295,6 +302,7 @@ class Context implements BuilderAwareInterface
->node($property, 'context')
->isRequired()
->attribute('context_type', $type)
->attribute('provision', $this->getProvision())
->end()
->end();
}
......@@ -325,7 +333,7 @@ class Context implements BuilderAwareInterface
// If type is empty, it's because it's in the ServerContext
if (empty($info['type'])) {
$server = Provision::getContext($info['server']);
$server = $this->getProvision()->getContext($info['server']);
$service_type = ucfirst($server->getService($service)->type);
}
else {
......
......@@ -3,6 +3,7 @@
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;
......@@ -46,9 +47,9 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
// 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 = Provision::getContext($this->properties['platform'], $provision);
// 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;
......
......@@ -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()
......
......@@ -62,6 +62,16 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
*/
private $commands = [];
/**
* @var \Aegir\Provision\Context[]
*/
private $contexts = [];
/**
* @var array[]
*/
private $context_files = [];
/**
* Provision constructor.
*
......@@ -106,6 +116,46 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
$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) {
......@@ -188,39 +238,19 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
}
/**
* Load all contexts into Context objects.
* Return all available contexts.
*
* @return array
* @return array|Context
*/
static function getAllContexts($name = '', Provision $provision = 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'], $provision);
public function getAllContexts($name = '') {
if ($name && isset($this->contexts[$name])) {
return $this->contexts[$name];
}
if ($name && isset($contexts[$name])) {
return $contexts[$name];
}
elseif ($name && !isset($contexts[$name])) {
elseif ($name && !isset($this->contexts[$name])) {
return NULL;
}
else {
return $contexts;
return $this->contexts;
}
}
......@@ -231,9 +261,9 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
* @return mixed
* @throws \Exception
*/
static public function getAllServers($service = NULL) {
protected function getAllServers($service = NULL) {
$servers = [];
$context_files = self::getAllContexts();
$context_files = $this->getAllContexts();
if (empty($context_files)) {
throw new \Exception('No server contexts found. Use `provision save` to create one.');
}
......@@ -272,14 +302,36 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
* @return array|\Aegir\Provision\Context
* @throws \Exception
*/
static public function getContext($name, Provision $provision = NULL) {
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(Provision::getAllContexts($name, $provision))) {
if (empty($this->context_files[$name])) {
throw new \Exception('Context not found with name: ' . $name);
}
return Provision::getAllContexts($name, $provision);
return$this->context_files[$name];
}
/**
......@@ -306,7 +358,7 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
* @param $type
* @return array
*/
static function checkServiceRequirements($type) {
public function checkServiceRequirements($type) {
$class_name = Context::getClassName($type);
// @var $context Context
......@@ -315,7 +367,7 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
$services = [];
foreach ($service_requirements as $service) {
try {
if (empty(Provision::getAllServers($service))) {
if (empty($this->getAllServers($service))) {
$services[$service] = 0;
}
else {
......
<?php
namespace Aegir\Provision\Service\Http\Php;
use Robo\Common\ExecCommand;
use Robo\Common\ExecTrait;
class PhpServer extends \Robo\Task\Development\PhpServer {
/**
* @var int
*/
protected $port;
/**
* @var string
*/
protected $host = '127.0.0.1';
/**
* @var string
*/
protected $command = 'php -S %s:%d ';
/**
* @param int $port
*/
public function __construct($port)
{
$this->port = $port;
if (strtolower(PHP_OS) === 'linux') {
$this->command = 'exec php -S %s:%d ';
}
}
/**
* @param string $host
*
* @return $this
*/
public function host($host)
{
$this->host = $host;
return $this;
}
/**
* @param string $path
*
* @return $this
*/
public function dir($path)
{
$this->command .= "-t $path";
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return sprintf($this->command, $this->host, $this->port);
}
public function run() {
$this->executeCommand($this->getCommand());
}
public function getProcess() {
return $this->process;
}
public function task() {
}
}
\ No newline at end of file
<?php
namespace Aegir\Provision;
interface ServiceInterface
{
/**
* Triggered on `provision verify` command on Site contexts.
*/
public function verifySite();
/**
* Triggered on `provision verify` command on Platform contexts.
*/
public function verifyPlatform();
/**
* Triggered on `provision verify` command on Site contexts.
*/
public function verifyServer();
}
\ No newline at end of file
......@@ -29,15 +29,15 @@ class ServiceSubscription {
$service_name
) {
$this->setContext($context);
$this->server = Provision::getContext($server, $context->getProvision());
$this->setProvision($context->getProvision());
$this->server = $this->getProvision()->getContext($server);
$this->service = $this->server->getService($service_name);
$this->type = $this->server->getService($service_name)->type;
if (isset($context->config['service_subscriptions'][$service_name]['properties'])) {
$this->properties = $context->config['service_subscriptions'][$service_name]['properties'];
}
$this->setProvision($context->getProvision());
}
public function verify() {
......