Commit 3a0f1486 authored by Jon Pugh's avatar Jon Pugh

Merge branch '4.x-robo-collections-verify' of...

Merge branch '4.x-robo-collections-verify' of github.com:aegir-project/provision into 4.x-robo-collections-verify
parents ae3f1d94 32f6aa00
......@@ -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\Property;
use Aegir\Provision\Provision;
use Aegir\Provision\Service;
use Symfony\Component\Console\Exception\InvalidOptionException;
......@@ -263,11 +264,16 @@ class SaveCommand extends Command
$class = '\Aegir\Provision\Context\\' . ucfirst($this->input->getOption('context_type')) . "Context";
$options = $class::option_documentation();
$properties = $this->askForRequiredContexts();
foreach ($options as $name => $description) {
foreach ($options as $name => $property) {
if (!empty($properties[$name])) {
continue;
}
// Allows option_documentation to return array of strings for simple properties.
if ( !$property instanceof Property) {
$property = Provision::newProperty($property);
}
// If option does not exist, ask for it.
if (!empty($this->input->getOption($name))) {
......@@ -275,7 +281,7 @@ class SaveCommand extends Command
$this->io->comment("Using option {$name}={$properties[$name]}");
}
else {
$properties[$name] = $this->io->ask("$name ($description)");
$properties[$name] = $this->io->ask("{$name}({$property->description})", $property->default, $property->validate);
}
}
return $properties;
......
......@@ -6,6 +6,7 @@
namespace Aegir\Provision;
use Aegir\Provision\Common\ContextAwareTrait;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
......@@ -16,16 +17,7 @@ use Symfony\Component\Filesystem\Filesystem;
*/
class Configuration {
/**
* Provision 4.x
*/
/**
* A \Aegir\Provision\Context object this configuration relates to.
*
* @var \Aegir\Provision\Context
*/
public $context = NULL;
use ContextAwareTrait;
/**
* A \Aegir\Provision\Service object this configuration relates to.
......@@ -98,13 +90,13 @@ class Configuration {
* An associative array to potentially manipulate in process() and make
* available as variables to the template.
*/
function __construct($context, $service, $data = array()) {
function __construct($context, $service = NULL, $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->setContext($context);
$this->service = $service;
$this->fs = new Filesystem();
......@@ -232,7 +224,7 @@ class Configuration {
* 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(Service $service) {
function write() {
// Make directory structure if it does not exist.
$filename = $this->filename();
......@@ -249,7 +241,10 @@ class Configuration {
if ($filename && is_writeable(dirname($filename))) {
// manipulate data before passing to template.
$this->process();
$service->processConfiguration($this);
if ($this->service instanceof Service) {
$this->service->processConfiguration($this);
}
if ($template = $this->load_template()) {
// Make sure we can write to the file
......
......@@ -153,7 +153,7 @@ class Config extends ProvisionConfig
*
* @return string
*/
protected function getHomeDir()
static public function getHomeDir()
{
$home = getenv('HOME');
if (!$home) {
......@@ -171,7 +171,7 @@ class Config extends ProvisionConfig
/**
* Determine the user running provision.
*/
public function getScriptUser() {
static public function getScriptUser() {
$real_script_user = posix_getpwuid(posix_geteuid());
return $real_script_user['name'];
}
......
......@@ -49,10 +49,90 @@ class PlatformContext extends ContextSubscriber implements ConfigurationInterfac
static function option_documentation()
{
$options = [
'root' => 'platform: path to a Drupal installation',
'makefile' => 'platform: drush makefile to use for building the platform if it doesn\'t already exist',
'make_working_copy' => 'platform: Specifiy TRUE to build the platform with the Drush make --working-copy option.',
'git_url' => 'platform: Git repository remote URL.',
'root' =>
Provision::newProperty()
->description('platform: path to the Drupal installation. You may use a relative or absolute path.')
->defaultValue(getcwd())
->required(TRUE)
->validate(function($path) {
if (strpos($path, '/') !== 0) {
$path = getcwd() . DIRECTORY_SEPARATOR . $path;
}
return $path;
})
,
'makefile' =>
Provision::newProperty()
->description('platform: Drush makefile to use for building the platform. May be a path or URL.')
->required(FALSE)
->validate(function($makefile) {
if (empty($makefile)) {
return $makefile;
}
$parsed = parse_url($makefile);
// If parsed is empty, it couldn't be read as a URL or filename.
if (empty($parsed)) {
throw new \RuntimeException("The makefile at {$makefile} could not be read.");
}
// If array is only path, it is a file path.
elseif (count(array_keys($parsed)) == 1 && isset($parsed['path'])) {
if (is_readable($parsed['path'])) {
return $makefile;
}
else {
throw new \RuntimeException("The makefile at {$makefile} could not be read.");
}
}
// Otherwise, makefile is a URL. Check if we can access it.
else {
try {
$content = @file_get_contents($makefile);
if ($content === false) {
throw new \RuntimeException("The makefile at {$makefile} could not be read.");
} else {
return $makefile;
}
}
catch (\Exception $e) {
throw new \RuntimeException("The makefile at {$makefile} could not be read.");
}
}
return $makefile;
})
,
'make_working_copy' =>
Provision::newProperty()
->description('platform: Specifiy TRUE to build the platform with the Drush make --working-copy option.')
->required(FALSE)
,
'git_url' =>
Provision::newProperty()
->description('platform: Git repository remote URL.')
->required(FALSE)
->validate(function($git_url) {
Provision::getProvision()->io()->comment('Checking git remote...');
// Use git ls-remote to detect a valid and accessible git URL.
$result = Provision::getProvision()->getTasks()->taskExec('git ls-remote')
->arg($git_url)
->silent(!Provision::getProvision()->getOutput()->isVerbose())
->run();
if (!$result->wasSuccessful()) {
throw new \RuntimeException("Unable to connect to git remote $git_url. Please check access and try again.");
}
Provision::getProvision()->io()->successLite('Connected to git remote.');
// @TODO: Parse brances and tags.
return $git_url;
})
,
];
return $options;
......@@ -97,7 +177,22 @@ class PlatformContext extends ContextSubscriber implements ConfigurationInterfac
});
}
elseif (!$this->fs->exists($this->getProperty('root')) && $this->getProperty('makefile')) {
$tasks['platform.make'] = $this->getProvision()->getTasks()->taskLog('Building platforms from make not yet supported.', 'warning');
$tasks['platform.make'] = $this->getProvision()->newTask()
->success('Deployed platform from makefile.')
->failure('Unable to deploy platform from makefile.')
->execute(function () {
$this->getProvision()->io()->warningLite('Root path does not exist. Creating platform from makefile ' . $this->getProperty('git_url') . ' in ' . $this->getProperty('root'));
$this->getProvision()->getTasks()->taskExec("drush make")
->arg($this->getProperty('makefile'))
->arg($this->getProperty('root'))
->silent(!$this->getProvision()->getOutput()->isVerbose())
->run()
;
});
}
return $tasks;
......
......@@ -2,7 +2,10 @@
namespace Aegir\Provision\Context;
use Aegir\Provision\Console\Config;
use Aegir\Provision\ContextProvider;
use Aegir\Provision\Property;
use Aegir\Provision\Provision;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
......@@ -20,18 +23,43 @@ class ServerContext extends ContextProvider implements ConfigurationInterface
*/
public $type = 'server';
const TYPE = 'server';
/**
* @return string|Property[]
*/
static function option_documentation()
{
$options = [
'remote_host' => 'server: host name; default localhost',
'script_user' => 'server: OS user name; default current user',
'aegir_root' => 'server: Aegir root; default '.getenv('HOME'),
'master_url' => 'server: Hostmaster URL',
return [
'remote_host' =>
Provision::newProperty()
->description('server: host name')
->required(TRUE)
->defaultValue('localhost')
->validate(function($remote_host) {
// If remote_host doesn't resolve to anything, warn the user.
$ip = gethostbynamel($remote_host);
if (empty($ip)) {
throw new \RuntimeException("Hostname $remote_host does not resolve to an IP address. Please try again.");
}
return $remote_host;
}),
'script_user' =>
Provision::newProperty()
->description('server: OS user name')
->required(TRUE)
->defaultValue(Config::getScriptUser()),
'aegir_root' =>
Provision::newProperty()
->description('server: aegir user home directory')
->required(TRUE)
->defaultValue(Config::getHomeDir()),
// @TODO: Why do server contexts need a master_url?
'master_url' =>
Provision::newProperty()
->description('server: Hostmaster URL')
->required(FALSE)
];
return $options;
}
......
......@@ -24,7 +24,7 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
const TYPE = 'site';
/**
* @var \Aegir\Provision\Context\SiteContext
* @var \Aegir\Provision\Context\PlatformContext
*/
public $platform;
......@@ -53,6 +53,9 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
// Add platform http service subscription.
$this->serviceSubscriptions['http'] = $this->platform->getSubscription('http');
$this->serviceSubscriptions['http']->setContext($this);
$uri = $this->getProperty('uri');
$this->properties['site_path'] = "sites/{$uri}";
}
......@@ -90,7 +93,131 @@ class SiteContext extends ContextSubscriber implements ConfigurationInterface
$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');
$this->getProvision()->io()->newLine();
$tasks['site.prepare'] = $this->getProvision()->newTask()
->success('Prepared directories and settings file. NOT YET.')
->failure('Failed to prepare directories.')
/**
* There are many ways to do this...
* This way is very verbose and I cannot figure out how to quiet it down.
*/
// ->execute($this->getProvision()->getTasks()->taskFilesystemStack()
// ->mkdir("$path/sites/$uri/files")
// ->chmod("$path/sites/$uri/files", 02770)
// ->chgrp("$path/sites/$uri/files", $this->getServices('http')->getProperty('web_group'))
/**
* This way is quiet.
*
* @see verify.provision.inc
* @see drush_provision_drupal_pre_provision_verify()
*/
->execute(function() {
return 0;
$uri = $this->getProperty('uri');
$path = $this->platform->getProperty('root');
// @TODO: These folders are how aegir works now. We might want to rethink what folders are created.
// Directories set to 755
$this->fs->mkdir([
"$path/sites/$uri",
], 0755);
// Directories set to 02775
$this->fs->mkdir([
"$path/sites/$uri/themes",
"$path/sites/$uri/modules",
"$path/sites/$uri/libraries",
], 02775);
// Directories set to 02775
$this->fs->mkdir([
"$path/sites/$uri/files",
"$path/sites/$uri/files/tmp",
"$path/sites/$uri/files/images",
"$path/sites/$uri/files/pictures",
"$path/sites/$uri/files/css",
"$path/sites/$uri/files/js",
"$path/sites/$uri/files/ctools",
"$path/sites/$uri/files/imagecache",
"$path/sites/$uri/files/locations",
"$path/sites/$uri/files/styles",
"$path/sites/$uri/private",
"$path/sites/$uri/private/config",
"$path/sites/$uri/private/config/sync",
"$path/sites/$uri/private/files",
"$path/sites/$uri/private/temp",
"$path/sites/$uri/private/temp",
], 02770);
// Change certain folders to be in web server group.
$this->fs->chgrp([
"$path/sites/$uri/files",
"$path/sites/$uri/files/tmp",
"$path/sites/$uri/files/images",
"$path/sites/$uri/files/pictures",
"$path/sites/$uri/files/css",
"$path/sites/$uri/files/js",
"$path/sites/$uri/files/ctools",
"$path/sites/$uri/files/imagecache",
"$path/sites/$uri/files/locations",
"$path/sites/$uri/files/styles",
"$path/sites/$uri/private",
"$path/sites/$uri/private/config",
"$path/sites/$uri/private/config/sync",
"$path/sites/$uri/private/files",
"$path/sites/$uri/private/temp",
"$path/sites/$uri/private/temp",
], $this->getSubscription('http')->service->getProperty('web_group'));
// Copy Drupal's default settings.php file into place.
$this->fs->copy("$path/sites/default/default.settings.php", "$path/sites/$uri/settings.php");
});
$tasks['site.settings'] = $this->getProvision()->newTask()
->success('Provision did not write your settings.php automatically. You must do this yourself now. Use `provision status {name}` to view the correct credentials.')
->failure('This wont fail, its a dummy placeholder.')
->execute(function () {
return 0;
})
;
return [];
// FROM verify.provision.inc drush_provision_drupal_pre_provision_verify() line 118
// drush_set_option('packages', _scrub_object(provision_drupal_system_map()), 'site');
// // This is the actual drupal provisioning requirements.
// _provision_drupal_create_directories();
// _provision_drupal_maintain_aliases();
// _provision_drupal_ensure_htaccess_update();
// // Requires at least the database settings to complete.
//
// _provision_drupal_create_settings_file();
//
// // If this is the hostmaster site, save the ~/.drush/drushrc.php file.
// if (d()->root == d('@hostmaster')->root && d()->uri == d('@hostmaster')->uri) {
// $aegir_drushrc = new Provision_Config_Drushrc_Aegir();
// $aegir_drushrc->write();
// }
//
// provision_drupal_push_site(drush_get_option('override_slave_authority', FALSE));
//
return $tasks;
}
/**
* Return a list of folders to create in the Drupal root.
*
* @TODO: Move this to the to-be-created DrupalPlatform class.
*/
function siteFolders($uri = 'default') {
return [
"sites/$uri",
"sites/$uri/files",
];
}
}
<?php
namespace Aegir\Provision;
/**
* Class Property
*
* Use this to create dynamic properties for contexts.
*
* For example:
*
* <?php
* static function option_documentation()
* {
* return [
* 'remote_host' =>
* Provision::newProperty()
* ->description('server: host name')
* ->required(TRUE)
* ->default('localhost'),
* ->validate(function($remote_host) {
* // If remote_host doesn't resolve to anything, warn the user.
* $ip = gethostbynamel($remote_host);
* if (empty($ip)) {
* throw new \RuntimeException("Hostname $remote_host does not resolve to an IP address. Please try again.");
* }
* return $remote_host;
* }),
* ];
* }
* ?>
*
* @package Aegir\Provision
*/
class Property {
public $description = '';
public $default = NULL;
public $required = FALSE;
public $validate;
/**
* Allow "backwards" compatibility: return the description when converting to a string.
* @return string
*/
function __toString()
{
return $this->description;
}
/**
* Property constructor.
*
* Set description and default validate callable.
*
* @param null $description
*/
public function __construct($description = NULL) {
$this->description($description);
$this->validate(function ($answer) {
if ($this->required && empty($answer)) {
throw new \RuntimeException('Property is required.');
}
else {
return $answer;
}
});
return $this;
}
/**
* Set the description of this property.
*
* @param string $description
*
* @return $this
*/
public function description($description) {
$this->description = $description;
return $this;
}
/**
* Set the default value of this property.
*
* If value is callable, will set the return value of the callable as the default value.
*
* @param string|callable $default
*
* @return $this
*/
public function defaultValue($default) {
if (is_callable($default)) {
$this->default = $default();
}
else {
$this->default = $default;
}
return $this;
}
/**
* Set if this Property is required or not.
*
* @param bool $required
*
* @return $this
*/
public function required($required = TRUE) {
$this->required = $required;
return $this;
}
/**
* Set the validation function for this property.
*
* @param callable $callable
*
* @return $this
*/
public function validate($callable) {
$this->validate = $callable;
return $this;
}
}
\ No newline at end of file
......@@ -428,8 +428,11 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger
public function getScriptUid() {
return posix_getuid();
}
public function newTask() {
static public function newTask() {
return new Task();
}
static public function newProperty($description = '') {
return new Property($description);
}
}
......@@ -32,6 +32,7 @@ use Robo\Task\Docker\Remove;
use Robo\Task\Docker\Run;
use Robo\Task\Docker\Start;
use Robo\Task\Docker\Stop;
use Robo\Task\Filesystem\FilesystemStack;
use Robo\Task\Vcs\GitStack;
/**
......@@ -213,6 +214,14 @@ class ProvisionTasks implements ConfigAwareInterface, LoggerAwareInterface, Buil
return $this->task(DockerExec::class, $cidOrResult);
}
/**
* @return FilesystemStack
*/
public function taskFilesystemStack()
{
return $this->task(FilesystemStack::class);
}
/**
* @return \Robo\Task\Vcs\GitStack
*/
......
......@@ -201,7 +201,7 @@ class Service implements BuilderAwareInterface
foreach ($this->getConfigurations()[$context->type] as $configuration_class) {
try {
$config = new $configuration_class($context, $this);
$config->write($this);
$config->write();
$context->getProvision()->getLogger()->info(
'Wrote {description} to {path}.', [
'description' => $config->description,
......
FROM ubuntu:14.04
RUN apt-get -qq -o Dpkg::Use-Pty=0 update && DEBIAN_FRONTEND=noninteractive apt-get -qq -o Dpkg::Use-Pty=0 install \
apache2 \
php5 \
php5-cli \
php5-gd \
php5-mysql \
php-pear \
php5-curl \
postfix \
sudo \
rsync \
git-core \
unzip \
wget \
mysql-client
# Use --build-arg option when running docker build to set these variables.
# If wish to "mount" a volume to your host, set AEGIR_UID and AEGIR_GID to your local user's UID.
# There are both ARG and ENV lines to make sure the value persists.
......@@ -25,6 +9,9 @@ RUN apt-get -qq -o Dpkg::Use-Pty=0 update && DEBIAN_FRONTEND=noninteractive apt-
ARG AEGIR_UID=1000
ENV AEGIR_UID ${AEGIR_UID:-1000}
ARG APACHE_UID=10000
ENV APACHE_UID ${APACHE_UID:-10000}
# The home directory for the aegir user.
ARG AEGIR_ROOT=/var/aegir
ENV AEGIR_ROOT ${AEGIR_ROOT:-/var/aegir}
......@@ -33,11 +20,32 @@ ENV AEGIR_ROOT ${AEGIR_ROOT:-/var/aegir}
ARG AEGIR_SERVER_NAME=server_master
ENV AEGIR_SERVER_NAME ${AEGIR_SERVER_NAME:-server_master}
RUN echo "Changing user www-data to UID $APACHE_UID and GID $APACHE_UID..."
RUN usermod -u $APACHE_UID www-data
RUN groupmod -g $APACHE_UID www-data
RUN apt-get -qq -o Dpkg::Use-Pty=0 update && DEBIAN_FRONTEND=noninteractive apt-get -qq -o Dpkg::Use-Pty=0 install \
apache2 \
php5 \
php5-cli \
php5-gd \
php5-mysql \
php-pear \
php5-curl \
postfix \
sudo \
rsync \
git-core \
unzip \
wget \
mysql-client
RUN echo "Creating user aegir with UID $AEGIR_UID and GID $AEGIR_GID and HOME $AEGIR_ROOT ..."
RUN addgroup --gid $AEGIR_UID aegir
RUN adduser --uid $AEGIR_UID --gid $AEGIR_UID --system --home $AEGIR_ROOT aegir
RUN adduser aegir www-data
RUN a2enmod rewrite
# Save a symlink to the /var/aegir/config/docker.conf file.
......
......@@ -133,7 +133,7 @@ class HttpApacheDockerService extends HttpApacheService
->success('Built new Docker image for Apache: ' . $this->containerTag)
->failure('Unable to build docker container with tag: ' . $this->containerTag)
->execute(function () use ($provision, $build_dir) {
return $this->getProvision()->getTasks()->taskDockerBuild($build_dir)
return $this->getProvision()->getTasks()->taskDockerBuild($build_dir)
->tag($this->containerTag)
->option(
'-f',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment