diff --git a/bin/provision-robo.php b/bin/provision-robo.php index f131d00981b28cea30a2f95cdc148673f72d9f6b..a3a804940c8e40740fb032c680430f91f0617e02 100644 --- a/bin/provision-robo.php +++ b/bin/provision-robo.php @@ -22,7 +22,7 @@ use Aegir\Provision\Common\NotSetupException; use Aegir\Provision\Console\ProvisionStyle; use Robo\Common\TimeKeeper; -use Symfony\Component\Console\Input\ArgvInput; +use Aegir\Provision\Console\ArgvInput; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Exception\CommandNotFoundException; @@ -68,10 +68,10 @@ try { } // Create the app. - $app = new \Aegir\Provision\Provision($config, $input, $output); + $provision = new \Aegir\Provision\Provision($config, $input, $output); // Run the app. - $status_code = $app->run($input, $output); + $status_code = $provision->run($input, $output); } catch (Exception $e) { diff --git a/src/Provision/Application.php b/src/Provision/Application.php index 03391d1a192cba40d1dca9850c5ae6fcb0c0db2f..19c302e4ba36e0116a68cd070b3615a57a5131e1 100644 --- a/src/Provision/Application.php +++ b/src/Provision/Application.php @@ -176,7 +176,7 @@ class Application extends BaseApplication $exitCode = parent::doRunCommand($command, $input, $output); return $exitCode; } - + /** * {@inheritdoc} * @@ -187,9 +187,9 @@ class Application extends BaseApplication $inputDefinition = parent::getDefaultInputDefinition(); $inputDefinition->addOption( new InputOption( - '--target', - '-t', - InputOption::VALUE_NONE, + '--context', + '-c', + InputOption::VALUE_OPTIONAL, 'The target context to act on.' ) ); diff --git a/src/Provision/Command.php b/src/Provision/Command.php index 8ddf5f68e8a84ffe1c15d7c6d04715431abc42a2..505be42d98c32d9a16b71ac22f3bb3a9ca7d4793 100644 --- a/src/Provision/Command.php +++ b/src/Provision/Command.php @@ -28,6 +28,8 @@ abstract class Command extends BaseCommand use ProvisionAwareTrait; use LoggerAwareTrait; + const CONTEXT_REQUIRED = FALSE; + /** * @var \Symfony\Component\Console\Input\InputInterface */ @@ -72,11 +74,11 @@ abstract class Command extends BaseCommand $this->io = new ProvisionStyle($input, $output); // Load active context if a command uses the argument. - if ($this->input->hasArgument('context_name') && !empty($this->input->getArgument('context_name'))) { + if ($this->input->getOption('context') && !empty($this->input->getOption('context'))) { try { // Load context from context_name argument. - $this->context_name = $this->input->getArgument('context_name'); + $this->context_name = $this->input->getOption('context'); $this->context = $this->getProvision()->getContext($this->context_name); } catch (\Exception $e) { @@ -94,9 +96,9 @@ abstract class Command extends BaseCommand } // If context_name is not specified, ask for it. - elseif ($this->input->hasArgument('context_name') && $this->getDefinition()->getArgument('context_name')->isRequired() && empty($this->input->getArgument('context_name'))) { + elseif ($this::CONTEXT_REQUIRED && empty($this->input->getOption('context'))) { $this->askForContext(); - $this->input->setArgument('context_name', $this->context_name); + $this->input->setOption('context', $this->context_name); try { $this->context = $this->getProvision()->getContext($this->context_name); diff --git a/src/Provision/Command/SaveCommand.php b/src/Provision/Command/SaveCommand.php index d3c1f8678070922c60eea445c049e7f8103d14f6..ef1c5ace8350f05a5a3c43f29f0b8a6aa4e4628b 100644 --- a/src/Provision/Command/SaveCommand.php +++ b/src/Provision/Command/SaveCommand.php @@ -27,7 +27,12 @@ use Symfony\Component\Console\Output\OutputInterface; */ class SaveCommand extends Command { - + + /** + * This command needs a context. + */ + const CONTEXT_REQUIRED = TRUE; + /** * @var string */ @@ -61,11 +66,6 @@ class SaveCommand extends Command protected function getCommandDefinition() { $inputDefinition = ServicesCommand::getCommandOptions(); - $inputDefinition[] = new InputArgument( - 'context_name', - InputArgument::REQUIRED, - 'Context to save' - ); $inputDefinition[] = new InputOption( 'context_type', null, @@ -135,7 +135,7 @@ class SaveCommand extends Command protected function initialize(InputInterface $input, OutputInterface $output) { parent::initialize($input,$output); - $this->context_name = $input->getArgument('context_name'); + $this->context_name = $input->getOption('context'); $this->context_type = $input->getOption('context_type'); } @@ -199,7 +199,7 @@ class SaveCommand extends Command $options['type'] = $this->context_type; $class = Context::getClassName($this->input->getOption('context_type')); - $this->context = new $class($input->getArgument('context_name'), $this->getProvision(), $options); + $this->context = new $class($input->getOption('context'), $this->getProvision(), $options); } else { $this->getProvision()->io()->helpBlock("Editing context {$this->context->name}...", ProvisionStyle::ICON_EDIT); @@ -257,7 +257,7 @@ class SaveCommand extends Command $this->io->warningLite('Context not saved.'); return; } -// $command = 'drush provision-save '.$input->getArgument('context_name'); +// $command = 'drush provision-save '.$input->getOption('context'); // $this->process($command); // If editing a context, exit here. @@ -437,7 +437,7 @@ class SaveCommand extends Command } $command = $this->getApplication()->find('services'); $arguments = [ - 'context_name' => $this->input->getArgument('context_name'), + 'context_name' => $this->input->getOption('context'), 'sub_command' => 'add', ]; while ($this->io->confirm('Add a service?')) { @@ -468,7 +468,7 @@ class SaveCommand extends Command $command = $this->getApplication()->find('services'); $arguments = [ - 'context_name' => $this->input->getArgument('context_name'), + 'context_name' => $this->input->getOption('context'), 'sub_command' => 'add', 'service' => $type, ]; diff --git a/src/Provision/Command/ServicesCommand.php b/src/Provision/Command/ServicesCommand.php index 1e1b1f5ef4438fe3a3a44e863e3d148637da9e8f..c7774441f66083744ce4859ca8b5ea236049be12 100644 --- a/src/Provision/Command/ServicesCommand.php +++ b/src/Provision/Command/ServicesCommand.php @@ -28,6 +28,11 @@ use Symfony\Component\Console\Output\OutputInterface; class ServicesCommand extends Command { + /** + * This command needs a context. + */ + const CONTEXT_REQUIRED = TRUE; + /** * "list" (default), "add", "remove", or "configure" * @var string @@ -45,7 +50,8 @@ class ServicesCommand extends Command ->setHelp( 'Use this command to add new services to servers, or to add service subscriptions to platforms and sites.' ) - ->setDefinition($this->getCommandDefinition()); + ->setDefinition($this->getCommandDefinition()) + ; } /** @@ -56,11 +62,7 @@ class ServicesCommand extends Command protected function getCommandDefinition() { $inputDefinition = $this::getCommandOptions(); - $inputDefinition[] = new InputArgument( - 'context_name', - InputArgument::REQUIRED, - 'Server to work on.' - ); + $inputDefinition[] = new InputArgument( 'sub_command', InputArgument::OPTIONAL, @@ -83,7 +85,6 @@ class ServicesCommand extends Command InputOption::VALUE_OPTIONAL, 'The name of the service type to use.' ); - return new InputDefinition($inputDefinition); } @@ -130,14 +131,15 @@ class ServicesCommand extends Command InputInterface $input, OutputInterface $output ) { - - if ($input->getArgument('context_name') == 'add') { - $this->sub_command = $input->getArgument('context_name'); - $input->setArgument('context_name', NULL); - } - else { - $this->sub_command = $input->getArgument('sub_command'); - } +// +// if ($input->getOption('context_name') == 'add') { +// $this->sub_command = $input->getArgument('context_name'); +// $input->setArgument('context_name', NULL); +// } +// else { +// $this->sub_command = $input->getArgument('sub_command'); +// } + $this->sub_command = $input->getArgument('sub_command'); parent::initialize( $input, diff --git a/src/Provision/Command/StatusCommand.php b/src/Provision/Command/StatusCommand.php index 9e1e5ac4ca6eaa0fd8fdd486758e6976fbe0bf4c..d43e3778e75804f395cb28d0bd8c5fb695367857 100644 --- a/src/Provision/Command/StatusCommand.php +++ b/src/Provision/Command/StatusCommand.php @@ -32,13 +32,6 @@ class StatusCommand extends Command ->setName('status') ->setDescription('Display system status.') ->setHelp('Lists helpful information about your system.') - ->setDefinition([ - new InputArgument( - 'context_name', - InputArgument::OPTIONAL, - 'Context to show info for.' - ) - ]) ; } @@ -49,14 +42,14 @@ class StatusCommand extends Command { $this->getProvision(); - if ($input->getArgument('context_name')) { + if ($input->getOption('context')) { $rows = [['Configuration File', $this->context->config_path]]; foreach ($this->context->getProperties() as $name => $value) { if (is_string($value)) { $rows[] = [$name, $value]; } } - $this->io->table(['Provision Context:', $input->getArgument('context_name')], $rows); + $this->io->table(['Provision Context:', $input->getOption('context')], $rows); // Display services. $this->context->showServices($this->io); diff --git a/src/Provision/Command/VerifyCommand.php b/src/Provision/Command/VerifyCommand.php index 52a4b532f640ad2f18d57981ff549c2f9f426f0b..99fce94808696abca7d91930e30a0faa779b9073 100644 --- a/src/Provision/Command/VerifyCommand.php +++ b/src/Provision/Command/VerifyCommand.php @@ -25,6 +25,11 @@ use Symfony\Component\Console\Output\OutputInterface; class VerifyCommand extends Command { + /** + * This command needs a context. + */ + const CONTEXT_REQUIRED = TRUE; + /** * {@inheritdoc} */ @@ -47,11 +52,6 @@ class VerifyCommand extends Command protected function getCommandDefinition() { $inputDefinition = []; - $inputDefinition[] = new InputArgument( - 'context_name', - InputArgument::REQUIRED, - 'Context to verify' - ); return new InputDefinition($inputDefinition); } diff --git a/src/Provision/Console/ArgvInput.php b/src/Provision/Console/ArgvInput.php new file mode 100644 index 0000000000000000000000000000000000000000..72392eff7aaf3e9f4b2fc6ec0a15ed3e75c92bd1 --- /dev/null +++ b/src/Provision/Console/ArgvInput.php @@ -0,0 +1,37 @@ +activeContextName = $context_name; + } + // If --context option is used, use that. + elseif ($argv_filtered = array_filter($argv, function ($key) { + return strpos($key, '--context') === 0; + })) { + $context_option = array_pop($argv_filtered); + $context_name = substr($context_option, strlen('--context=')); + $this->activeContextName = $context_name; + } + parent::__construct($argv, $definition); + } +} \ No newline at end of file diff --git a/src/Provision/Context.php b/src/Provision/Context.php index 9b43fab79f556df1168cf44229e6926c89b70692..10b5e54cc0dcc2f31947f2b9b6e37c3dfa985872 100644 --- a/src/Provision/Context.php +++ b/src/Provision/Context.php @@ -53,7 +53,7 @@ class Context implements BuilderAwareInterface */ public $type = null; const TYPE = null; - + /** * The role of this context, either 'subscriber' or 'provider'. * @@ -85,7 +85,7 @@ class Context implements BuilderAwareInterface * @var \Symfony\Component\Filesystem\Filesystem */ public $fs; - + /** * Context constructor. * @@ -98,13 +98,13 @@ class Context implements BuilderAwareInterface $options = []) { $this->name = $name; - + $this->setProvision($provision); $this->setBuilder($this->getProvision()->getBuilder()); - + $this->loadContextConfig($options); $this->prepareServices(); - + $this->fs = new Filesystem(); } @@ -143,14 +143,14 @@ class Context implements BuilderAwareInterface } } } - + $this->properties['type'] = $this->type; $this->properties['name'] = $this->name; - + $configs[] = $this->properties; $this->config = $processor->processConfiguration($this, $configs); - + } catch (\Exception $e) { throw new InvalidOptionException( strtr("There is an error with the configuration for !type '!name'. Check the file !file and try again. \n \nError: !message", [ @@ -392,7 +392,7 @@ class Context implements BuilderAwareInterface ->end() ->end(); } - + // Load contextRequirements into config as ContextNodes. foreach ($this->contextRequirements() as $property => $type) { $root_node @@ -405,14 +405,14 @@ class Context implements BuilderAwareInterface ->end() ->end(); } - + if (method_exists($this, 'configTreeBuilder')) { $this->configTreeBuilder($root_node); } return $tree_builder; } - + /** * Prepare either services or service subscriptions config tree. */ @@ -457,15 +457,15 @@ class Context implements BuilderAwareInterface * Output a list of all services for this context. */ public function showServices(DrupalStyle $io) { - + $services = $this->isProvider()? $this->getServices(): $this->getSubscriptions(); if (!empty($services)) { $rows = []; - + $headers = $this->isProvider()? ['Services']: ['Service', 'Server', 'Type']; - + foreach ($services as $name => $service) { if ($this::ROLE == 'provider') { $rows[] = [$name, $service->type]; @@ -535,11 +535,11 @@ class Context implements BuilderAwareInterface */ public function save() { - + // Create config folder if it does not exist. $fs = new Filesystem(); $dumper = new Dumper(); - + try { $fs->dumpFile($this->config_path, $dumper->dump($this->getProperties(), 10)); return true; @@ -564,7 +564,7 @@ class Context implements BuilderAwareInterface return false; } } - + /** * Retrieve the class name of a specific context type. * @@ -581,7 +581,7 @@ class Context implements BuilderAwareInterface // public function verify() { // return "Provision Context"; // } - + /** * Verify this context. * @@ -620,9 +620,9 @@ class Context implements BuilderAwareInterface } $tasks = []; } - + $result = $collection->run(); - + if ($result->wasSuccessful()) { $this->getProvision()->io()->success('Verification Complete!'); } @@ -793,7 +793,7 @@ class Context implements BuilderAwareInterface public static function contextRequirements() { return []; } - + /** * Whether or not this context is a provider. * @@ -915,4 +915,15 @@ class Context implements BuilderAwareInterface }); return $process->getExitCode(); } + + /** + * return list of command classes. + */ + function getServiceCommandClasses() { + $classes = []; + foreach ($this->servicesInvoke('getCommandClasses') as $class) { + $classes += $class; + } + return $classes; + } } diff --git a/src/Provision/Provision.php b/src/Provision/Provision.php index 1b159976556cf560b842dc763fe04ff0496e187d..3dd88833bbb5aafbcbac4f5256ba60bf0d11334c 100644 --- a/src/Provision/Provision.php +++ b/src/Provision/Provision.php @@ -8,6 +8,7 @@ use Aegir\Provision\Commands\ExampleCommands; use Aegir\Provision\Console\ConsoleOutput; use Aegir\Provision\Console\ProvisionStyle; +use Aegir\Provision\Console\ArgvInput; use Aegir\Provision\Context\ServerContext; use Aegir\Provision\Robo\ProvisionCollectionBuilder; use Aegir\Provision\Robo\ProvisionExecutor; @@ -30,7 +31,6 @@ use Robo\Robo; use Robo\Runner as RoboRunner; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\Console\Exception\InvalidOptionException; -use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; @@ -88,6 +88,11 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger * @var \Aegir\Provision\Context[] */ public $contexts = []; + + /** + * @var \Aegir\Provision\Context The currently active context. + */ + public $activeContext; /** * @var array[] @@ -150,6 +155,10 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger $this->console->setProvision($this); $this->loadAllContexts(); + + if ($input->activeContextName) { + $this->activeContext = $this->getContext($input->activeContextName); + } } /** @@ -208,7 +217,14 @@ class Provision implements ConfigAwareInterface, ContainerAwareInterface, Logger } public function run(InputInterface $input, OutputInterface $output) { - $status_code = $this->runner->run($input, $output); + + $commandClasses = []; + + // Look up any robofiles in each service. + if ($this->activeContext && method_exists($this->activeContext, 'getServiceCommandClasses')) { + $commandClasses += $this->activeContext->getServiceCommandClasses(); + } + $status_code = $this->runner->run($input, $output, $this->getApplication(), $commandClasses); return $status_code; } diff --git a/src/Provision/Service/Http/ApacheDocker/ApacheDockerCommands.php b/src/Provision/Service/Http/ApacheDocker/ApacheDockerCommands.php new file mode 100644 index 0000000000000000000000000000000000000000..1cd139668bab76c88d6f19220b397dc1c212bc8a --- /dev/null +++ b/src/Provision/Service/Http/ApacheDocker/ApacheDockerCommands.php @@ -0,0 +1,80 @@ + 'The site or server to run the docker command in.', + 'docker-compose-options' => 'the options to pass to docker compose ' + ] + ) { + /** @var \Aegir\Provision\Application $application */ + $application = $this->getContainer()->get('application'); + + /** @var \Robo\Log\RoboLogger $logger */ + $logger = $this->getContainer()->get('logger'); + + /** @var ArgvInput $input */ + $input = $this->getContainer()->get('input'); + + $logger->info('Hi! This is a robo command.'); + + $process = new \Symfony\Component\Process\Process("docker-compose {$docker_compose_command}"); + + $wd = $this->getContainer()->get('application')->getProvision()->activeContext == 'server'? + $this->getContainer()->get('application')->getProvision()->activeContext->server_config_path: + $this->getContainer()->get('application')->getProvision()->activeContext->service('http')->provider->server_config_path; + + $process->setWorkingDirectory($wd); + $process->setTty(TRUE); + $process->run(); + } + + + /** + * Stream logs from the containers using docker-compose logs -f + */ + public function dockerLogs() { + $process = new \Symfony\Component\Process\Process("docker-compose logs -f"); + + $wd = $this->getContainer()->get('application')->getProvision()->activeContext == 'server'? + $this->getContainer()->get('application')->getProvision()->activeContext->server_config_path: + $this->getContainer()->get('application')->getProvision()->activeContext->service('http')->provider->server_config_path; + + $process->setWorkingDirectory($wd); + $process->setTty(TRUE); + $process->run(); } + + /** + * Enter a bash shell in the web server container. + */ + public function dockerShell() { + $process = new \Symfony\Component\Process\Process("docker-compose exec http bash"); + + $wd = $this->getContainer()->get('application')->getProvision()->activeContext == 'server'? + $this->getContainer()->get('application')->getProvision()->activeContext->server_config_path: + $this->getContainer()->get('application')->getProvision()->activeContext->service('http')->provider->server_config_path; + + $process->setWorkingDirectory($wd); + $process->setTty(TRUE); + $process->run(); + } +} \ No newline at end of file diff --git a/src/Provision/Service/Http/HttpApacheDockerService.php b/src/Provision/Service/Http/HttpApacheDockerService.php index 6aa6f438c195b81c545f874f033de3bbb1f98193..cc8b8413003a47dba3dbd2f218604345e47a81c8 100644 --- a/src/Provision/Service/Http/HttpApacheDockerService.php +++ b/src/Provision/Service/Http/HttpApacheDockerService.php @@ -470,4 +470,10 @@ ENV; $lines[] = " CustomLog /var/log/provision.log custom"; return implode("\n", $lines); } + + public function getCommandClasses() { + return [ + \Aegir\Provision\Service\Http\ApacheDocker\ApacheDockerCommands::class + ]; + } }