Skip to content
Commits on Source (36)
......@@ -54,7 +54,7 @@ if ($debug){
}
try {
$application = new Application();
$application = new Application($input, $output);
$application->run();
}
catch (\Exception $e) {
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "c8f06b1cc28f9638bdc92415ed78c328",
"content-hash": "25f4161ac19dde865bcef9fa9d341b03",
"packages": [
{
"name": "consolidation/annotated-command",
......
......@@ -8,11 +8,14 @@ use Aegir\Provision\Command\ShellCommand;
use Aegir\Provision\Command\StatusCommand;
use Aegir\Provision\Command\VerifyCommand;
use Aegir\Provision\Console\Config;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
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;
......@@ -49,10 +52,26 @@ class Application extends BaseApplication
* @var string
*/
const DEFAULT_TIMEZONE = 'America/New_York';
public function __construct()
/**
* @var LoggerInterface
*/
public $logger;
/**
* Application constructor.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @throws \Exception
*/
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->logger = new ConsoleLogger($output,
[LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL]
);
// If no timezone is set, set Default.
if (empty(ini_get('date.timezone'))) {
date_default_timezone_set($this::DEFAULT_TIMEZONE);
......@@ -105,7 +124,7 @@ class Application extends BaseApplication
$commands[] = new ListCommand();
$commands[] = new SaveCommand();
$commands[] = new ServicesCommand();
$commands[] = new ShellCommand();
// $commands[] = new ShellCommand();
$commands[] = new StatusCommand();
$commands[] = new VerifyCommand();
......@@ -137,16 +156,22 @@ class Application extends BaseApplication
*
* @return array
*/
function getAllContexts() {
function getAllContexts($name = '') {
$contexts = [];
$finder = new \Symfony\Component\Finder\Finder();
$finder->files()->name('*.yml')->in($this->config->get('config_path') . '/provision');
$finder->files()->name('*' . $name . '.yml')->in($this->config->get('config_path') . '/provision');
foreach ($finder as $file) {
list($context_type, $context_name) = explode('.', $file->getFilename());
$class = '\Aegir\Provision\Context\\' . ucfirst($context_type) . "Context";
$contexts[$context_name] = new $class($context_name, $this->config->all());
$contexts[$context_name] = new $class($context_name, $this->config->all(), $this);
}
if ($name) {
return $contexts[$name];
}
else {
return $contexts;
}
return $contexts;
}
/**
......@@ -158,9 +183,50 @@ class Application extends BaseApplication
* @throws \Exception
*/
function getContext($name) {
if (empty($this->getAllContexts()[$name])) {
if (empty($this->getAllContexts($name))) {
throw new \Exception('Context not found with name: ' . $name);
}
return $this->getAllContexts()[$name];
return $this->getAllContexts($name);
}
/**
* Load all server contexts.
*
* @param null $service
* @return mixed
* @throws \Exception
*/
public function getAllServers($service = NULL) {
$servers = [];
$contexts = $this->getAllContexts();
if (empty($contexts)) {
throw new \Exception('No contexts found. Use `provision save` to create one.');
}
foreach ($contexts as $name => &$context) {
if ($context->type == 'server') {
$servers[$name] = $context;
}
}
return $servers;
}
/**
* 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;
}
}
......@@ -3,9 +3,11 @@
namespace Aegir\Provision;
use Drupal\Console\Core\Style\DrupalStyle;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Drupal\Console\Core\Command\Shared\CommandTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
/**
......
......@@ -68,7 +68,7 @@ class SaveCommand extends Command
);
// Load all Aegir\Provision\Context and inject their options.
// @TODO: Figure out a way to do discovery to include all classes that inherit Aegir\Provision\Context
// @TODO: Use CommandFileDiscovery to include all classes that inherit Aegir\Provision\Context
$contexts[] = SiteContext::option_documentation();
$contexts[] = PlatformContext::option_documentation();
$contexts[] = ServerContext::option_documentation();
......@@ -113,7 +113,7 @@ class SaveCommand extends Command
}
$properties = $this->askForContextProperties();
$class = Context::getClassName($this->input->getOption('context_type'));
$this->context = new $class($input->getArgument('context_name'), $this->getApplication()->getConfig()->all(), $properties);
$this->context = new $class($input->getArgument('context_name'), $this->getApplication()->getConfig()->all(), $this->getApplication(), $properties);
}
// Delete context config.
......@@ -144,11 +144,6 @@ class SaveCommand extends Command
$this->io->error("Unable to save configuration to {$this->context->config_path}. ");
}
}
$output->writeln(
"Context Object: ".print_r($this->context,1)
);
// $command = 'drush provision-save '.$input->getArgument('context_name');
// $this->process($command);
}
......
......@@ -8,6 +8,7 @@ use Aegir\Provision\Context\PlatformContext;
use Aegir\Provision\Context\ServerContext;
use Aegir\Provision\Context\SiteContext;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputDefinition;
......@@ -81,9 +82,9 @@ class ServicesCommand extends Command
$input,
$output
);
if (isset($this->context->type) && $this->context->type != 'server') {
throw new \Exception('Context must be a server.');
}
// if (isset($this->context->type) && $this->context->type != 'server') {
// throw new \Exception('Context must be a server.');
// }
$this->sub_command = $input->getArgument('sub_command');
}
......@@ -120,19 +121,50 @@ class ServicesCommand extends Command
$this->io->comment("Add Services");
$service = $this->io->choice('Which service?', $this->context->getServiceOptions());
// Then ask which service type
$service_type = $this->io->choice('Which service type?', $this->context->getServiceTypeOptions($service));
// Then ask for all options.
$properties = $this->askForServiceProperties($service);
// If server, ask which service type.
if ($this->context->type == 'server') {
if (empty($this->context->getServiceTypeOptions($service))) {
throw new \Exception("There was no class found for service $service. Create one named \\Aegir\\Provision\\Service\\{$service}Service");
}
$service_type = $this->io->choice('Which service type?', $this->context->getServiceTypeOptions($service));
// Then ask for all options.
$properties = $this->askForServiceProperties($service);
$this->io->info("Adding $service service $service_type...");
$this->io->info("Adding $service service $service_type...");
try {
$this->context->config['services'][$service] = [
$services_key = 'services';
$service_info = [
'type' => $service_type,
'properties' => $properties,
];
}
// All other context types are associating with servers that provide the service.
else {
if (empty($this->getApplication()->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->io->choice('Which server?', $this->getApplication()->getServerOptions($service));
// Then ask for all options.
$server_context = $this->getApplication()->getContext($server);
$properties = $this->askForServiceProperties($service);
$this->io->info("Using $service service from server $server...");
$services_key = 'service_subscriptions';
$service_info = [
'server' => $server,
];
}
try {
$this->context->config[$services_key][$service] = $service_info;
if (!empty($properties)) {
$this->context->config[$services_key][$service]['properties'] = $properties;
}
$this->context->save();
$this->io->success('Service saved to Context!');
}
......@@ -149,8 +181,9 @@ class ServicesCommand extends Command
private function askForServiceProperties($service) {
$class = $this->context->getAvailableServices($service);
$method = "{$this->context->type}_options";
$options = $class::option_documentation();
$options = $class::{$method}();
$properties = [];
foreach ($options as $name => $description) {
// If option does not exist, ask for it.
......
<?php
/**
* @file
* Provision configuration generation classes.
*/
namespace Aegir\Provision;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* Class Configuration
*
* @package Aegir\Provision
*/
class Configuration {
/**
* Provision 4.x
*/
/**
* A \Aegir\Provision\Context object this configuration relates to.
*
* @var \Aegir\Provision\Context
*/
public $context = NULL;
/**
* A \Aegir\Provision\Service object this configuration relates to.
*
* @var \Aegir\Provision\Service
*/
public $service = NULL;
/**
* @var Filesystem
*/
public $fs;
/**
* LEGACY
*/
/**
* Template file, a PHP file which will have access to $this and variables
* as defined in $data.
*/
public $template = NULL;
/**
* Associate array of variables to make available to the template.
*/
public $data = array();
/**
* If set, replaces file name in log messages.
*/
public $description = NULL;
/**
* Octal Unix mode for permissons of the created file.
*/
protected $mode = NULL;
/**
* Unix group name for the created file.
*/
protected $group = NULL;
/**
* An optional data store class to instantiate for this config.
*/
protected $data_store_class = NULL;
/**
* The data store.
*/
public $store = NULL;
/**
* Forward $this->... to $this->context->...
* object.
*/
function __get($name) {
if (isset($this->context)) {
return $this->context->$name;
}
}
/**
* Constructor, overriding not recommended.
*
* @param $context
* An alias name for d(), the Provision_Context that this configuration
* is relevant to.
* @param $data
* An associative array to potentially manipulate in process() and make
* available as variables to the template.
*/
function __construct($context, $service, $data = array()) {
if (is_null($this->template)) {
throw new Exception(dt("No template specified for: %class", array('%class' => get_class($this))));
}
// Accept both a reference and an alias name for the context.
$this->context = $context;
$this->service = $service;
$this->fs = new Filesystem();
if (sizeof($data)) {
$this->data = $data;
}
if (!is_null($this->data_store_class) && class_exists($this->data_store_class)) {
$class = $this->data_store_class;
$this->store = new $class($context, $data);
}
}
/**
* Process and add to $data before writing the configuration.
*
* This is a stub to be implemented by subclasses.
*/
function process() {
if (!empty($this->context->getProperties())) {
$this->data = $this->context->getProperties();
}
// @TODO: Remove legacy code.
// if (is_object($this->store)) {
// $this->data['records'] = array_filter(array_merge($this->store->loaded_records, $this->store->records));
// }
// return TRUE;
}
/**
* The filename where the configuration is written.
*
* This is a stub to be implemented by subclasses.
*/
function filename() {
return FALSE;
}
/**
* Load template from filename().
*
* @see hook_provision_config_load_templates()
* @see hook_provision_config_load_templates_alter()
*/
private function load_template() {
return file_get_contents(__DIR__ . '/Service/' . ucfirst($this->service->getName()) . '/' . ucfirst($this->service->getType()) . '/Configuration/' . $this->template);
// Allow other Drush commands to change the template used first.
// $templates = drush_command_invoke_all('provision_config_load_templates', $this);
// // Ensure that templates is at least an array.
// if (!is_array($templates)) {
// $templates = array();
// }
// // Allow other Drush commands to alter the templates from other commands.
//// drush_command_invoke_all_ref('provision_config_load_templates_alter', $templates, $this);
// if (!empty($templates) && is_array($templates)) {
// foreach ($templates as $file) {
// if (is_readable($file)) {
// drush_log(dt('Template loaded from hook(s): :file', array(
// ':file' => $file,
// )));
// return file_get_contents($file);
// }
// }
// }
//
// // If we've got this far, then try to find a template from this class or
// // one of its parents.
// if (isset($this->template)) {
// $class_name = get_class($this);
// while ($class_name) {
// // Iterate through the config file's parent classes until we
// // find the template file to use.
// $base_dir = provision_class_directory($class_name);
// $file = $base_dir . '/' . $this->template;
//
// if (is_readable($file)) {
// drush_log(dt('Template loaded from Provision Config class :class_name: :file', array(
// ':class_name' => $class_name,
// ':file' => $file,
// )));
// return file_get_contents($file);
// }
//
// $class_name = get_parent_class($class_name);
// }
// }
//
// // We've failed to find a template if we've reached this far.
// drush_log(dt('No template found for Provision Config class: ', array(':class' => get_class($this))), 'warning');
// return FALSE;
}
/**
* Render template, making variables available from $variables associative
* array.
*/
private function render_template($template, $variables) {
// Allow modules to alter the variables before writing to the template.
// @see hook_provision_config_variables_alter()
// drush_command_invoke_all_ref('provision_config_variables_alter', $variables, $template, $this);
// drush_errors_off();
extract($variables, EXTR_SKIP); // Extract the variables to a local namespace
ob_start(); // Start output buffering
eval('?>' . $template); // Generate content
$contents = ob_get_contents(); // Get the contents of the buffer
ob_end_clean(); // End buffering and discard
// drush_errors_on();
return $contents; // Return the contents
}
/**
* Write out this configuration.
*
* 1. Make sure parent directory exists and is writable.
* 2. Load template with load_template().
* 3. Process $data with process().
* 4. Make existing file writable if necessary and possible.
* 5. Render template with $this and $data and write out to filename().
* 6. If $mode and/or $group are set, apply them for the new file.
*/
function write() {
// Make directory structure if it does not exist.
$filename = $this->filename();
if ($filename && !$this->fs->exists([dirname($filename)])) {
try {
$this->fs->mkdir([dirname($filename)]);
}
catch (IOException $e) {
throw new \Exception("Could not create directory " . dirname($filename) . ": " . $e->getMessage());
}
}
$status = FALSE;
if ($filename && is_writeable(dirname($filename))) {
// manipulate data before passing to template.
$this->process();
if ($template = $this->load_template()) {
// Make sure we can write to the file
if (!is_null($this->mode) && !($this->mode & 0200) && $this->fs->exists(($filename))) {
try {
$this->fs->chmod([$filename], $this->mode);
}
catch (IOException $e) {
throw new \Exception('Could not change permissions of ' . $filename . ' to ' . $this->mode);
}
}
try {
$this->fs->dumpFile($filename, $this->render_template($template, $this->data));
}
catch (IOException $e) {
throw new \Exception('Could not write file to ' . $filename);
}
// Change the permissions of the file if needed
if (!is_null($this->mode)) {
try {
$this->fs->chmod([$filename], $this->mode);
}
catch (IOException $e) {
throw new \Exception('Could not change permissions of ' . $filename . ' to ' . $this->mode);
}
}
if (!is_null($this->group)) {
try {
$this->fs->chgrp([$filename], $this->group);
}
catch (IOException $e) {
throw new \Exception('Could not change group ownership of ' . $filename . ' to ' . $this->group);
}
}
}
}
return $status;
}
// allow overriding w.r.t locking
function file_put_contents($filename, $text) {
provision_file()->file_put_contents($filename, $text)
->succeed('Generated config in file_put_contents()' . (empty($this->description) ? $filename : $this->description), 'success');
}
/**
* Remove configuration file as specified by filename().
*/
function unlink() {
return provision_file()->unlink($this->filename())->status();
}
}
......@@ -6,6 +6,9 @@
namespace Aegir\Provision;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Drupal\Console\Core\Style\DrupalStyle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Filesystem\Exception\IOException;
......@@ -49,6 +52,22 @@ class Context
*/
protected $properties = [];
/**
* @var array
* A list of services associated with this context.
*/
protected $services = [];
/**
* @var \Aegir\Provision\Application;
*/
public $application;
/**
* @var LoggerInterface
*/
public $logger;
/**
* Context constructor.
*
......@@ -56,10 +75,12 @@ class Context
* @param $console_config
* @param array $options
*/
function __construct($name, $console_config, $options = [])
function __construct($name, $console_config, Application $application, $options = [])
{
$this->name = $name;
$this->application = $application;
$this->loadContextConfig($console_config, $options);
$this->prepareServices();
}
/**
......@@ -99,13 +120,142 @@ class Context
}
}
/**
* Load Service classes from config into Context..
*/
protected function prepareServices() {
if (isset($this->config['services'])) {
foreach ($this->config['services'] as $service_name => $service) {
$service_name = ucfirst($service_name);
$service_type = ucfirst($service['type']);
$service_class = "\\Aegir\\Provision\\Service\\{$service_name}\\{$service_name}{$service_type}Service";
$this->services[strtolower($service_name)] = new $service_class($service, $this);
}
}
elseif (isset($this->config['service_subscriptions'])) {
foreach ($this->config['service_subscriptions'] as $service_name => $service) {
$this->servers[$service_name] = $server = $this->application->getContext($service['server']);
$this->services[$service_name] = new ServiceSubscription($this, $server, $service_name);
}
}
else {
$this->services = [];
}
}
/**
* Loads all available \Aegir\Provision\Service classes
*
* @return array
*/
public function getAvailableServices($service = '') {
// Load all service classes
$classes = [];
$discovery = new CommandFileDiscovery();
$discovery->setSearchPattern('*Service.php');
$servicesFiles = $discovery->discover(__DIR__ .'/Service', '\Aegir\Provision\Service');
foreach ($servicesFiles as $serviceClass) {
// If this is a server, show all services. If it is not, but service allows this type of context, load it.
if ($this->type == 'server' || in_array($this->type, $serviceClass::allowedContexts())) {
$classes[$serviceClass::SERVICE] = $serviceClass;
}
}
if ($service && isset($classes[$service])) {
return $classes[$service];
}
elseif ($service && !isset($classes[$service])) {
throw new \Exception("No service with name $service was found.");
}
else {
return $classes;
}
}
/**
* Lists all available services as a simple service => name array.
* @return array
*/
public function getServiceOptions() {
$options = [];
$services = $this->getAvailableServices();
foreach ($services as $service => $class) {
$options[$service] = $class::SERVICE_NAME;
}
return $options;
}
/**
* @return array
*/
protected function getAvailableServiceTypes($service, $service_type = NULL) {
// Load all service classes
$classes = [];
$discovery = new CommandFileDiscovery();
$discovery->setSearchPattern(ucfirst($service) . '*Service.php');
$serviceTypesFiles = $discovery->discover(__DIR__ .'/Service/' . ucfirst($service), '\Aegir\Provision\Service\\' . ucfirst($service));
foreach ($serviceTypesFiles as $serviceTypeClass) {
$classes[$serviceTypeClass::SERVICE_TYPE] = $serviceTypeClass;
}
if ($service_type && isset($classes[$service_type])) {
return $classes[$service_type];
}
elseif ($service_type && !isset($classes[$service_type])) {
throw new \Exception("No service type with name $service_type was found.");
}
else {
return $classes;
}
}
/**
* Lists all available services as a simple service => name array.
* @return array
*/
public function getServiceTypeOptions($service) {
$options = [];
$service_types = $this->getAvailableServiceTypes($service);
foreach ($service_types as $service_type => $class) {
$options[$service_type] = $class::SERVICE_TYPE_NAME;
}
return $options;
}
/**
* Return all services for this context.
*
* @return array
*/
public function getServices() {
return $this->services;
}
/**
* Return all services for this context.
*
* @return array
*/
public function getService($type) {
if (isset($this->services[$type])) {
return $this->services[$type];
}
else {
throw new \Exception("Service '$type' does not exist.");
}
}
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$tree_builder = new TreeBuilder();
$root_node = $tree_builder->root('server');
$root_node = $tree_builder->root($this->type);
$root_node
->children()
->scalarNode('name')
......@@ -113,6 +263,28 @@ class Context
->end()
->end();
// Load Services
if ($this->type == 'server') {
$services_key = 'services';
$services_property = 'type';
}
else {
$services_key = 'service_subscriptions';
$services_property = 'server';
}
$root_node
->children()
->arrayNode($services_key)
->prototype('array')
->children()
->scalarNode($services_property)
->isRequired(true)
->end()
->append($this->addServiceProperties($services_key))
->end()
->end();
// @TODO: Figure out how we can let other classes add to Context properties.
foreach ($this->option_documentation() as $name => $description) {
$root_node
......@@ -130,6 +302,77 @@ class Context
return $tree_builder;
}
/**
* Append Service class options_documentation to config tree.
*/
public function addServiceProperties($property_name = 'services')
{
$builder = new TreeBuilder();
$node = $builder->root('properties');
// Load config tree from Service type classes
if (!empty($this->getProperty($property_name)) && !empty($this->getProperty($property_name))) {
foreach ($this->getProperty($property_name) as $service => $info) {
// If type is empty, it's because it's in the ServerContext
if (empty($info['type'])) {
$server = $this->application->getContext($info['server']);
$service_type = ucfirst($server->getService($service)->type);
}
else {
$service_type = ucfirst($info['type']);
}
$service = ucfirst($service);
$class = "\Aegir\Provision\Service\\{$service}\\{$service}{$service_type}Service";
$method = "{$this->type}_options";
foreach ($class::{$method}() as $name => $description) {
$node
->children()
->scalarNode($name)->end()
->end()
->end();
}
}
}
return $node;
}
/**
* Output a list of all services for this context.
*/
public function showServices(DrupalStyle $io) {
if (!empty($this->getServices())) {
$is_server = $this->type == 'server';
$rows = [];
$headers = $is_server?
['Services']:
['Service', 'Server', 'Type'];
foreach ($this->getServices() as $name => $service) {
if ($is_server) {
$rows[] = [$name, $service->type];
}
else {
$rows[] = [
$name,
$service->server->name,
$service->server->getService($name)->type
];
}
// Show all properties.
if (!empty($service->properties )) {
foreach ($service->properties as $name => $value) {
$rows[] = [' ' . $name, $value];
}
}
}
$io->table($headers, $rows);
}
}
/**
* Return all properties for this context.
*
......@@ -145,7 +388,12 @@ class Context
* @return array
*/
public function getProperty($name) {
return $this->properties[$name];
if (isset($this->properties[$name])) {
return $this->properties[$name];
}
else {
return NULL;
}
}
/**
......@@ -189,7 +437,23 @@ class Context
return '\Aegir\Provision\Context\\' . ucfirst($type) . "Context";
}
// public function verify() {
// return "Provision Context";
// }
/**
* Verify this context.
*
* Running `provision verify CONTEXT` triggers this method.
*
* Collect all services for the context and run the verify() method on them
*/
public function verify() {
return "Provision Context";
// Run verify method on all services.
foreach ($this->getServices() as $service) {
$service->verify();
}
}
}
......@@ -2,7 +2,9 @@
namespace Aegir\Provision\Context;
use Aegir\Provision\Application;
use Aegir\Provision\Context;
use Aegir\Provision\Service\Http\Apache\Configuration\PlatformConfiguration;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
......@@ -18,7 +20,35 @@ class PlatformContext extends Context implements ConfigurationInterface
* @var string
*/
public $type = 'platform';
/**
* @var \Aegir\Provision\Context\ServerContext;
*/
public $web_server;
/**
* PlatformContext constructor.
*
* @param $name
* @param $console_config
* @param Application $application
* @param array $options
*/
function __construct($name, $console_config, Application $application, array $options = [])
{
parent::__construct($name, $console_config, $application, $options);
// Load "web_server" context.
if (isset($this->config['web_server'])) {
$this->web_server = $application->getContext($this->config['web_server']);
$this->web_server->logger = $application->logger;
}
else {
throw new \Exception('No web_server found.');
}
}
static function option_documentation()
{
$options = [
......@@ -31,4 +61,11 @@ class PlatformContext extends Context implements ConfigurationInterface
return $options;
}
// @TODO: Remove. This should be handled by Services now.
// public function verify() {
// parent::verify();
// $this->logger->info('Verifying Web Server...');
// $this->web_server->verify();
// }
}
......@@ -2,6 +2,7 @@
namespace Aegir\Provision\Context;
use Aegir\Provision\Application;
use Aegir\Provision\Context;
use Consolidation\AnnotatedCommand\CommandFileDiscovery;
use Drupal\Console\Core\Style\DrupalStyle;
......@@ -23,27 +24,6 @@ class ServerContext extends Context implements ConfigurationInterface
*/
public $type = 'server';
/**
* @var array
* A list of services needed for this context.
*/
protected $services = [];
/**
* ServerContext constructor.
*
* @param $name
* @param $console_config
* @param array $options
*/
function __construct($name, $console_config, array $options = [])
{
parent::__construct($name, $console_config, $options);
if (isset($this->config['services'])) {
$this->services = $this->config['services'];
}
}
static function option_documentation()
{
$options = [
......@@ -56,162 +36,4 @@ class ServerContext extends Context implements ConfigurationInterface
return $options;
}
/**
* Loads all available \Aegir\Provision\Service classes
*
* @return array
*/
public function getAvailableServices($service = NULL) {
// Load all service classes
$classes = [];
$discovery = new CommandFileDiscovery();
$discovery->setSearchPattern('*Service.php');
$servicesFiles = $discovery->discover(__DIR__ .'/../Service', '\Aegir\Provision\Service');
foreach ($servicesFiles as $serviceClass) {
$classes[$serviceClass::SERVICE] = $serviceClass;
}
if ($service && isset($classes[$service])) {
return $classes[$service];
}
elseif ($service && !isset($classes[$service])) {
throw new \Exception("No service with name $service was found.");
}
else {
return $classes;
}
}
/**
* Lists all available services as a simple service => name array.
* @return array
*/
public function getServiceOptions() {
$options = [];
$services = $this->getAvailableServices();
foreach ($services as $service => $class) {
$options[$service] = $class::SERVICE_NAME;
}
return $options;
}
/**
* @return array
*/
protected function getAvailableServiceTypes($service, $service_type = NULL) {
// Load all service classes
$classes = [];
$discovery = new CommandFileDiscovery();
$discovery->setSearchPattern(ucfirst($service) . '*Service.php');
$serviceTypesFiles = $discovery->discover(__DIR__ .'/../Service/' . ucfirst($service), '\Aegir\Provision\Service\\' . ucfirst($service));
foreach ($serviceTypesFiles as $serviceTypeClass) {
$classes[$serviceTypeClass::SERVICE_TYPE] = $serviceTypeClass;
}
if ($service_type && isset($classes[$service_type])) {
return $classes[$service_type];
}
elseif ($service_type && !isset($classes[$service_type])) {
throw new \Exception("No service type with name $service_type was found.");
}
else {
return $classes;
}
}
/**
* Lists all available services as a simple service => name array.
* @return array
*/
public function getServiceTypeOptions($service) {
$options = [];
$service_types = $this->getAvailableServiceTypes($service);
foreach ($service_types as $service_type => $class) {
$options[$service_type] = $class::SERVICE_TYPE_NAME;
}
return $options;
}
/**
* Return all services for this context.
*
* @return array
*/
public function getServices() {
return $this->services;
}
/**
* Pass server specific config to Context configTreeBuilder.
*/
public function configTreeBuilder(&$root_node)
{
$root_node
->children()
->arrayNode('services')
->prototype('array')
->children()
->scalarNode('type')
->isRequired(true)
->end()
->append($this->addServiceProperties())
->end()
->end();
}
/**
* Append Service class options_documentation to config tree.
*/
public function addServiceProperties()
{
$builder = new TreeBuilder();
$node = $builder->root('properties');
// Load config tree from Service type classes
if (!empty($this->getProperty('services')) && !empty($this->getProperty('services'))) {
foreach ($this->getProperty('services') as $service => $info) {
$service = ucfirst($service);
$service_type = ucfirst($info['type']);
$class = "\Aegir\Provision\Service\\{$service}\\{$service}{$service_type}Service";
foreach ($class::option_documentation() as $name => $description) {
$node
->children()
->scalarNode($name)->end()
->end()
->end();
}
}
}
return $node;
}
public function verify() {
// parent::verify();
return "Server Context Verified: " . $this->name;
}
/**
* Output a list of all services for this service.
*/
public function showServices(DrupalStyle $io) {
if (!empty($this->getServices())) {
$rows = [];
foreach ($this->getServices() as $name => $service) {
$rows[] = [$name, $service['type']];
// Show all properties.
if (!empty($service['properties'] )) {
foreach ($service['properties'] as $name => $value) {
$rows[] = [' ' . $name, $value];
}
}
}
$io->table(['Services'], $rows);
}
}
}
......@@ -2,6 +2,7 @@
namespace Aegir\Provision\Context;
use Aegir\Provision\Application;
use Aegir\Provision\Context;
use Symfony\Component\Config\Definition\ConfigurationInterface;
......@@ -19,6 +20,33 @@ class SiteContext extends Context implements ConfigurationInterface
*/
public $type = 'site';
/**
* PlatformContext constructor.
*
* @param $name
* @param $console_config
* @param Application $application
* @param array $options
*/
function __construct($name, $console_config, Application $application, array $options = [])
{
parent::__construct($name, $console_config, $application, $options);
// Load "db_server" context.
// if (isset($this->config['service_subscriptions']['db'])) {
// $this->db_server = $application->getContext($this->config['service_subscriptions']['db']['server']);
// }
$this->logger = $application->logger;
if (isset($this->config['platform'])) {
$this->platform = $application->getContext(
$this->config['platform']
);
}
}
static function option_documentation()
{
return [
......@@ -35,6 +63,16 @@ class SiteContext extends Context implements ConfigurationInterface
];
}
public function verify() {
parent::verify();
// $this->db_server->service('db')->verify();
// $this->platform->verify();
// return "Site Context Verified: " . $this->name;
}
// /**
// * Write out this named context to an alias file.
// */
......
......@@ -11,7 +11,103 @@ namespace Aegir\Provision;
//require_once DRUSH_BASE_PATH . '/commands/core/rsync.core.inc';
class Service {
public $type;
public $properties;
function __construct($service_config, $context) {
$this->context = $context;
$this->type = $service_config['type'];
$this->properties = $service_config['properties'];
}
/**
* React to the `provision verify` command.
*/
function verify() {
$this->writeConfigurations();
}
/**
* List context types that are allowed to subscribe to this service.
* @return array
*/
static function allowedContexts() {
return [];
}
/**
* Write this service's configurations.
*/
protected function writeConfigurations() {
if (empty($this->getConfigurations()[$this->context->type])) {
return;
}
$this->context->application->logger->info('CONTEXT ' . $this->context->type);
foreach ($this->getConfigurations()[$this->context->type] as $configuration_class) {
$config = new $configuration_class($this->context, $this);
$config->write();
$this->context->application->logger->info('Wrote ' . $config->description . ' to ' . $config->filename());
}
}
/**
* Stub for this services configurations.
*/
protected function getConfigurations() {
return [];
}
/**
* Return the SERVICE_TYPE
* @return mixed
*/
public function getType() {
return $this::SERVICE_TYPE;
}
/**
* Return the SERVICE_TYPE
* @return mixed
*/
public function getName() {
return $this::SERVICE;
}
/**
* Return a list of user configurable options that this service provides to Server Context objects.
*/
static function server_options() {
return [];
// return [
// 'http_port' => 'The port which the web service is running on.',
// 'web_group' => 'server with http: OS group for permissions; working default will be attempted',
// ];
}
/**
* Return a list of user configurable options that this service provides to Platform Context objects.
*/
static function platform_options() {
return [];
// return [
// 'platform_extra_config' => 'Extra lines of configuration to add to this platform.',
// ];
}
/**
* Return a list of user configurable options that this service provides to Site Context objects.
*/
static function site_options() {
return [];
// return [
// 'site_mail' => 'The email address to use for the ServerAdmin configuration.',
// ];
}
/**
* LEGACY
*/
/**
* The server this service is associated to
*/
......@@ -62,9 +158,9 @@ class Service {
* This is used so that we can create methods for drush commands, and
* can fail safely.
*/
function __call($name, $args = array()) {
return provision::method_invoke($this, $name, $args);
}
// function __call($name, $args = array()) {
// return provision::method_invoke($this, $name, $args);
// }
function init() {
......@@ -316,10 +412,6 @@ class Service {
return FALSE;
}
function __construct($server) {
$this->server = is_object($server) ? $server : d($server);
}
/**
* Set the currently active context of the service.
*
......@@ -344,20 +436,10 @@ class Service {
function fetch($path = NULL) {
return $this->server->fetch($path);
}
function verify() {
return TRUE;
}
/**
* Return service-specific configuration options for help.
*
* @return
* array('option' => 'description')
*/
static function option_documentation() {
return array();
}
//
// function verify() {
// return TRUE;
// }
/**
* Save symlink for this server from /var/aegir/config/APPLICATION_NAME.conf -> /var/aegir/config/SERVER/APPLICATION_NAME.conf
......
This diff is collapsed.
<?php
/**
* @file Server.php
*
* Apache Configuration for Server Context.
* @see \Provision_Config_Apache_Server
* @see \Provision_Config_Http_Server
* @see \Provision_Config_Http_Server
*/
namespace Aegir\Provision\Service\Http\Apache\Configuration;
use Aegir\Provision\Configuration;
class PlatformConfiguration extends Configuration {
const SERVICE_TYPE = 'apache';
public $template = 'platform.tpl.php';
public $description = 'platform configuration file';
function filename() {
return $this->service->properties['http_platformd_path'] . '/' . ltrim($this->context->name, '@') . '.conf';
}
}
\ No newline at end of file
<?php
/**
* @file Server.php
*
* Apache Configuration for Server Context.
*
* This class represents the file at /var/aegir/config/apache.conf.
*
*
* @see \Provision_Config_Apache_Server
* @see \Provision_Config_Http_Server
* @see \Provision_Config_Http_Server
*/
namespace Aegir\Provision\Service\Http\Apache\Configuration;
use Aegir\Provision\Configuration;
class ServerConfiguration extends Configuration {
const SERVICE_TYPE = 'apache';
public $template = 'server.tpl.php';
public $description = 'web server configuration file';
function filename() {
if ($this->service->getType()) {
$file = $this->service->getType() . '.conf';
return $this->context->console_config['config_path'] . '/' . $this->context->name . '/' . $file;
}
else {
return FALSE;
}
}
function process()
{
parent::process();
$app_dir = $this->context->console_config['config_path'] . '/' . $this->service->getType();
$this->data['http_port'] = $this->service->properties['http_port'];
$this->data['include_statement'] = '# INCLUDE STATEMENT';
$this->data['http_pred_path'] = "{$app_dir}/pre.d";
$this->data['http_postd_path'] = "{$app_dir}/post.d";
$this->data['http_platformd_path'] = "{$app_dir}/platform.d";
$this->data['http_vhostd_path'] = "{$app_dir}/vhost.d";
$this->data['extra_config'] = "";
}
}
\ No newline at end of file
<?php
/**
* @file Site.php
*
* Apache Configuration for Server Context.
* @see \Provision_Config_Apache_Site
* @see \Provision_Config_Http_Site
*/
namespace Aegir\Provision\Service\Http\Apache\Configuration;
use Aegir\Provision\Configuration;
class SiteConfiguration extends Configuration {
const SERVICE_TYPE = 'apache';
public $template = 'vhost.tpl.php';
// The template file to use when the site has been disabled.
public $disabled_template = 'vhost_disabled.tpl.php';
public $description = 'virtual host configuration file';
function filename() {
$file = $this->uri . '.conf';
// return $this->service->properties['http_platformd_path'] . '/' . ltrim($this->context->name, '@') . '.conf';
return $this->context->console_config['config_path'] . '/' . $this->context->name . '/' . $file;
// return $this->context->config['config_path'];
// if (drush_get_option('provision_apache_conf_suffix', FALSE)) {
// return $this->data['http_vhostd_path'] . '/' . $this->uri . '.conf';
// }
// else {
// return $this->data['http_vhostd_path'] . '/' . $this->uri;
// }
}
function process() {
parent::process();
if ($this->aliases && !is_array($this->aliases)) {
$this->aliases = explode(",", $this->aliases);
}
$this->aliases = array_filter($this->aliases, 'trim');
if ($this->drush_aliases && !is_array($this->drush_aliases)) {
$this->drush_aliases = explode(",", $this->drush_aliases);
}
$this->drush_aliases = array_filter($this->drush_aliases, 'trim');
if (!$this->site_enabled) {
$this->template = $this->disabled_template;
}
$app_dir = $this->context->console_config['config_path'] . '/' . $this->service->getType();
// $this->data['http_port'] = $this->service->properties['http_port'];
// $this->data['include_statement'] = '# INCLUDE STATEMENT';
// $this->data['http_pred_path'] = "{$app_dir}/pre.d";
// $this->data['http_postd_path'] = "{$app_dir}/post.d";
// $this->data['http_platformd_path'] = "{$app_dir}/platform.d";
// $this->data['extra_config'] = "";
$this->data['http_vhostd_path'] = "{$app_dir}/vhost.d";
}
}
\ No newline at end of file
# Aegir web server configuration file
NameVirtualHost *:<?php print $http_port; ?>
<VirtualHost *:<?php print $http_port; ?>>
ServerName default
Redirect 404 /
</VirtualHost>
<IfModule !env_module>
LoadModule env_module modules/mod_env.so
</IfModule>
<IfModule !rewrite_module>
LoadModule rewrite_module modules/mod_rewrite.so
</IfModule>
<?php
//if (drush_get_option('provision_apache_conf_suffix', FALSE)) {
// $include_statement = 'IncludeOptional ';
// $include_suffix = '/*.conf';
//}
//else {
$include_statement = 'Include ';
$include_suffix = '';
//}
?>
# other configuration, not touched by aegir
# this allows you to override aegir configuration, as it is included before
<?php print $include_statement . $http_pred_path . $include_suffix ?>
# virtual hosts
<?php print $include_statement . $http_vhostd_path . $include_suffix ?>
# platforms
<?php print $include_statement . $http_platformd_path . $include_suffix ?>
# other configuration, not touched by aegir
# this allows to have default (for example during migrations) that are eventually overriden by aegir
<?php print $include_statement . $http_postd_path . $include_suffix ?>
<?php print $extra_config; ?>
<VirtualHost *:<?php print $http_port; ?>>
<?php if ($this->site_mail) : ?>
ServerAdmin <?php print $this->site_mail; ?>
<?php endif;?>
<?php
$aegir_root = drush_get_option('aegir_root');
if (!$aegir_root && $server->aegir_root) {
$aegir_root = $server->aegir_root;
}
?>
DocumentRoot <?php print $this->root; ?>
ServerName <?php print $this->uri; ?>
SetEnv db_type <?php print urlencode($db_type); ?>
SetEnv db_name <?php print urlencode($db_name); ?>
SetEnv db_user <?php print urlencode($db_user); ?>
SetEnv db_passwd <?php print urlencode($db_passwd); ?>
SetEnv db_host <?php print urlencode($db_host); ?>
SetEnv db_port <?php print urlencode($db_port); ?>
<?php
if (sizeof($this->aliases)) {
foreach ($this->aliases as $alias) {
print " ServerAlias " . $alias . "\n";
}
}
?>
<IfModule mod_rewrite.c>
RewriteEngine on
<?php
if ($this->redirection || $ssl_redirection) {
if ($ssl_redirection && !$this->redirection) {
print " # Redirect aliases in non-ssl to the same alias on ssl.\n";
print " RewriteRule ^/*(.*)$ https://%{HTTP_HOST}/$1 [NE,L,R=301]\n";
}
elseif ($ssl_redirection && $this->redirection) {
print " # Redirect all aliases + main uri to the main https uri.\n";
print " RewriteRule ^/*(.*)$ https://{$this->uri}/$1 [NE,L,R=301]\n";
}
elseif (!$ssl_redirection && $this->redirection) {
print " # Redirect all aliases to the main http url.\n";
print " RewriteCond %{HTTP_HOST} !^{$this->redirection}$ [NC]\n";
print " RewriteRule ^/*(.*)$ http://{$this->redirection}/$1 [NE,L,R=301]\n";
}
}
?>
RewriteRule ^/files/(.*)$ /sites/<?php print $this->uri; ?>/files/$1 [L]
RewriteCond <?php print $this->site_path; ?>/files/robots.txt -f
RewriteRule ^/robots.txt /sites/<?php print $this->uri; ?>/files/robots.txt [L]
</IfModule>
<?php print $extra_config; ?>
# Error handler for Drupal > 4.6.7
<Directory ~ "sites/.*/files">
<Files *>
SetHandler This_is_a_Drupal_security_line_do_not_remove
</Files>
Options None
Options +FollowSymLinks
# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
</Directory>
# Prevent direct reading of files in the private dir.
# This is for Drupal7 compatibility, which would normally drop
# a .htaccess in those directories, but we explicitly ignore those
<Directory "<?php print $this->site_path; ?>/private/" >
<Files *>
SetHandler This_is_a_Drupal_security_line_do_not_remove
</Files>
Deny from all
Options None
Options +FollowSymLinks
# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
</Directory>
<?php
$if_subsite = $this->data['http_subdird_path'] . '/' . $this->uri;
if (provision_hosting_feature_enabled('subdirs') && provision_file()->exists($if_subsite)->status()) {
print " Include " . $if_subsite . "/*.conf\n";
}
?>
</VirtualHost>
......@@ -19,4 +19,27 @@ class HttpApacheService extends HttpService
{
const SERVICE_TYPE = 'apache';
const SERVICE_TYPE_NAME = 'Apache';
/**
* Returns array of Configuration classes for this service.
*
* @see Provision_Service_http_apache::init_server();
*
* @return array
*/
public function getConfigurations()
{
$configs['server'][] = '\Aegir\Provision\Service\Http\Apache\Configuration\ServerConfiguration';
$configs['platform'][] = '\Aegir\Provision\Service\Http\Apache\Configuration\PlatformConfiguration';
$configs['site'][] = '\Aegir\Provision\Service\Http\Apache\Configuration\SiteConfiguration';
return $configs;
}
/**
* Respond to the `provision verify` command.
*/
public function verify() {
// print "VERIFY APACHE SERVER!";
parent::verify();
}
}