From 92d1e811b5e29505abd63db9c44906f707a538b5 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Fri, 8 Mar 2019 23:50:24 +0100 Subject: [PATCH 01/17] Display destination for put:file (Fixes #37) --- src/Command/PutFileCommand.php | 11 +++++++++-- src/ShellProvider/LocalShellProvider.php | 2 ++ src/ShellProvider/SshShellProvider.php | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Command/PutFileCommand.php b/src/Command/PutFileCommand.php index a845f14c..d141429c 100644 --- a/src/Command/PutFileCommand.php +++ b/src/Command/PutFileCommand.php @@ -55,10 +55,17 @@ protected function execute(InputInterface $input, OutputInterface $output) $context = new TaskContext($this, $input, $output); $context->set('sourceFile', $file); - $output->writeln('Put file `' . $file . '` into `' . $this->getHostConfig()['configName']. '`'); + $context->io()->comment('Putting file `' . $file . '` to `' . $this->getHostConfig()['configName']. '`'); $this->getMethods()->runTask('putFile', $this->getHostConfig(), $context); - return $context->getResult('exitCode', 0); + $return_code = $context->getResult('exitCode', 0); + if (!$return_code) { + $context->io()->success(sprintf( + '`%s` copied to `%s`', + $file, + $context->getResult('targetFile', 'unknown') + )); + } } } diff --git a/src/ShellProvider/LocalShellProvider.php b/src/ShellProvider/LocalShellProvider.php index 7b0de5c2..db8d2ba0 100644 --- a/src/ShellProvider/LocalShellProvider.php +++ b/src/ShellProvider/LocalShellProvider.php @@ -218,6 +218,8 @@ public function putFile(string $source, string $dest, TaskContextInterface $cont { $this->cd($context->getConfigurationService()->getFabfilePath()); $result = $this->run(sprintf('cp -r "%s" "%s"', $source, $dest)); + $context->setResult('targetFile', $dest); + return $result->succeeded(); } diff --git a/src/ShellProvider/SshShellProvider.php b/src/ShellProvider/SshShellProvider.php index 2d5f496f..bf62f62d 100644 --- a/src/ShellProvider/SshShellProvider.php +++ b/src/ShellProvider/SshShellProvider.php @@ -128,6 +128,8 @@ public function putFile(string $source, string $dest, TaskContextInterface $cont $command[] = $source; $command[] = $this->hostConfig['user'] . '@' . $this->hostConfig['host'] . ':' . $dest; + $context->setResult('targetFile', $dest); + return $this->runProcess($command, $context, false, true); } @@ -263,9 +265,7 @@ public function copyFileFrom( } else { $this->logger->warning('Could not copy file via SSH, try fallback'); } - } return parent::copyFileFrom($from_shell, $source_file_name, $target_file_name, $context, $verbose); } - } From 15d56c2baba49fe8d650a551a482d35f398fc17a Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 00:51:07 +0100 Subject: [PATCH 02/17] Include jump-host when running ssh:command if needed (fixes #36) --- src/Command/ShellCommandCommand.php | 12 +++++++++--- src/Method/SshMethod.php | 29 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/Command/ShellCommandCommand.php b/src/Command/ShellCommandCommand.php index 6c5651fe..31411a4a 100644 --- a/src/Command/ShellCommandCommand.php +++ b/src/Command/ShellCommandCommand.php @@ -5,6 +5,8 @@ use Phabalicious\Configuration\ConfigurationService; use Phabalicious\Configuration\HostConfig; use Phabalicious\Method\TaskContext; +use Phabalicious\Method\TaskContextInterface; +use Phabalicious\ShellProvider\ShellProviderInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -48,9 +50,13 @@ protected function execute(InputInterface $input, OutputInterface $output) // Allow methods to override the used shellProvider: $this->getMethods()->runTask('shell', $host_config, $context); + + /** @var ShellProviderInterface $shell */ $shell = $context->getResult('shell', $host_config->shell()); + $ssh_command = $context->getResult('ssh_command', $shell->getShellCommand()); - $output->writeln(implode(' ', $shell->getShellCommand())); - } + $context->io()->text('$ ' . implode(' ', $ssh_command)); -} \ No newline at end of file + return 0; + } +} diff --git a/src/Method/SshMethod.php b/src/Method/SshMethod.php index 9e765e10..0eb87a99 100644 --- a/src/Method/SshMethod.php +++ b/src/Method/SshMethod.php @@ -7,7 +7,6 @@ use Phabalicious\ShellProvider\ShellProviderFactory; use Phabalicious\ShellProvider\SshShellProvider; use Phabalicious\Validation\ValidationErrorBagInterface; -use webignition\ReadableDuration\ReadableDuration; class SshMethod extends BaseMethod implements MethodInterface { @@ -172,4 +171,30 @@ public function preflightTask(string $task, HostConfig $config, TaskContextInter $this->creatingTunnel = false; } -} \ No newline at end of file + public function shell(HostConfig $config, TaskContextInterface $context) + { + if (!empty($config['sshTunnel'])) { + $tunnel = $config['sshTunnel']; + $ssh_command = [ + 'ssh', + '-A', + '-J', + sprintf( + '%s@%s:%s', + $tunnel['bridgeUser'], + $tunnel['bridgeHost'], + $tunnel['bridgePort'] + ), + '-p', + $tunnel['destPort'], + sprintf( + '%s@%s', + $config['user'], + $tunnel['destHost'] + ) + ]; + + $context->setResult('ssh_command', $ssh_command); + } + } +} From 3d19dd75a2d25c48cc1ea00c85e847ffb3530621 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 00:52:22 +0100 Subject: [PATCH 03/17] drushVersion and drupalVersion can be set on the root level --- src/Method/DrushMethod.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Method/DrushMethod.php b/src/Method/DrushMethod.php index d68aae87..8f9fde31 100644 --- a/src/Method/DrushMethod.php +++ b/src/Method/DrushMethod.php @@ -84,8 +84,14 @@ public function getDefaultConfig(ConfigurationService $configuration_service, ar $config['database']['prefix'] = false; } - $config['drupalVersion'] = in_array('drush7', $host_config['needs']) ? 7 : 8; - $config['drushVersion'] = in_array('drush9', $host_config['needs']) ? 9 : 8; + $config['drupalVersion'] = in_array('drush7', $host_config['needs']) + ? 7 + : $configuration_service->getSetting('drupalVersion', 8); + + $config['drushVersion'] = in_array('drush9', $host_config['needs']) + ? 9 + : $configuration_service->getSetting('drushVersion', 8); + $config['supportsZippedBackups'] = true; $config['siteFolder'] = 'sites/default'; $config['filesFolder'] = 'sites/default/files'; From db5bbf448005cfc3be0b6614b04c4249a9b8f255 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Tue, 5 Feb 2019 15:14:38 +0100 Subject: [PATCH 04/17] Add test-case for variants --- src/Command/BaseCommand.php | 53 +++++++++++++++++-- src/Configuration/BlueprintConfiguration.php | 22 +++++++- .../variants-base-command-tests/.fabfile.yaml | 33 ++++++++++++ 3 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 tests/assets/variants-base-command-tests/.fabfile.yaml diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index bf93c55f..826bbca1 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -2,6 +2,7 @@ namespace Phabalicious\Command; +use http\Exception\InvalidArgumentException; use Phabalicious\Configuration\ConfigurationService; use Phabalicious\Configuration\HostConfig; use Phabalicious\Exception\BlueprintTemplateNotFoundException; @@ -11,6 +12,8 @@ use Phabalicious\Exception\ValidationFailedException; use Phabalicious\Exception\MissingHostConfigException; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Validation\ValidationErrorBag; +use Phabalicious\Validation\ValidationService; use Psr\Log\NullLogger; use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; @@ -47,6 +50,13 @@ protected function configure() InputOption::VALUE_OPTIONAL, 'Which blueprint to use', null + ) + ->addOption( + 'variants', + null, + InputOption::VALUE_OPTIONAL, + 'Runt the command on a given set of variants simultanously', + null ); parent::configure(); @@ -102,6 +112,10 @@ protected function execute(InputInterface $input, OutputInterface $output) if ($this->hostConfig->shell()) { $this->hostConfig->shell()->setOutput($output); } + + if ($input->hasOption('variants')) { + return $this->handleVariants($input->getOption('variants'), $input, $output); + } } catch (MissingHostConfigException $e) { $output->writeln('Could not find host-config named `' . $config_name . '`'); return 1; @@ -116,10 +130,6 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } - - - - /** * Get host config. * @@ -188,4 +198,39 @@ protected function startInteractiveShell(ShellProviderInterface $shell, array $c return $process; } + + /** + * Handle variants. + * + * @param $variants + * @param InputInterface $input + * @param OutputInterface $output + * @throws ValidationFailedException + */ + private function handleVariants($variants, InputInterface $input, OutputInterface $output) + { + $available_variants = $this->configuration->getBlueprints()->getVariants($this->hostConfig['configName']); + if (!$available_variants) { + throw new \InvalidArgumentException(sprintf( + 'Could not find variants for `%s` in `blueprints`', + $this->hostConfig['configName'] + )); + } + + if ($variants == 'all') { + $variants = $available_variants; + } else { + $variants = explode(',', $variants); + $not_found = array_filter($variants, function ($v) use ($available_variants) { + return !in_array($v, $available_variants); + }); + + if (!empty($not_found)) { + throw new \InvalidArgumentException(sprintf( + 'Could not find variants `%s` in `blueprints`', + implode('`, `', $not_found) + )); + } + } + } } diff --git a/src/Configuration/BlueprintConfiguration.php b/src/Configuration/BlueprintConfiguration.php index e24e3d13..fe91abfd 100644 --- a/src/Configuration/BlueprintConfiguration.php +++ b/src/Configuration/BlueprintConfiguration.php @@ -30,7 +30,6 @@ public function __construct(ConfigurationService $service) } } foreach ($this->configuration->getAllHostConfigs() as $key => $data) { - if (!empty($data['blueprint'])) { $this->templates['host:' . $key] = new BlueprintTemplate($this->configuration, $data['blueprint']); } @@ -91,4 +90,23 @@ public function expandVariants($blueprints) } } } -} \ No newline at end of file + + + /** + * Get all variants for a given config. + * + * @param $config_name + * @return bool|array + */ + public function getVariants($config_name) + { + $data = $this->configuration->getSetting('blueprints', []); + foreach ($data as $b) { + if ($b['configName'] == $config_name) { + return $b['variants']; + } + } + + return false; + } +} diff --git a/tests/assets/variants-base-command-tests/.fabfile.yaml b/tests/assets/variants-base-command-tests/.fabfile.yaml new file mode 100644 index 00000000..43ba0368 --- /dev/null +++ b/tests/assets/variants-base-command-tests/.fabfile.yaml @@ -0,0 +1,33 @@ +name: variants-tests + +requires: 3.0.0 + +needs: + - script + + + +hosts: + testMissingVariants: + rootFolder: "%fabfile.path%" + + test: + blueprint: + configName: test-%slug% + script: + - echo "%host.configName" + + + +blueprints: + - configName: test + variants: + - a + - b + - c + - d + - e + - f + - g + - h + From 4646aab2851ce9a6dfd821419af78c9f743f3ee0 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Tue, 5 Feb 2019 21:50:48 +0100 Subject: [PATCH 05/17] Implement parallel execution --- composer.json | 3 +- composer.lock | 175 +++++++++++++++++- src/Command/BaseCommand.php | 54 +++++- src/Command/InstallCommand.php | 11 +- src/Configuration/ConfigurationService.php | 7 + src/Utilities/ParallelExecutor.php | 77 ++++++++ src/Utilities/ParallelExecutorRun.php | 78 ++++++++ symfony.lock | 9 + tests/VariantBaseCommandTest.php | 88 +++++++++ .../variants-base-command-tests/.fabfile.yaml | 10 +- 10 files changed, 496 insertions(+), 16 deletions(-) create mode 100644 src/Utilities/ParallelExecutor.php create mode 100644 src/Utilities/ParallelExecutorRun.php create mode 100644 tests/VariantBaseCommandTest.php diff --git a/composer.json b/composer.json index d9c5f29a..b6e60bf0 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "ext-openssl": "*", "jakeasmith/http_build_url": "^1.0", "padraic/phar-updater": "^1.0", - "lesstif/php-jira-rest-client": "^1.35" + "lesstif/php-jira-rest-client": "^1.35", + "graze/parallel-process": "^0.8.1" }, "require-dev": { "symfony/phpunit-bridge": "^2.8|^3|^4.1", diff --git a/composer.lock b/composer.lock index 63f4b1ea..fb2c8141 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f7bccafd33b2508f929c7c85efb734c2", + "content-hash": "11989c13bcb0588ac113f3373ef4ba47", "packages": [ { "name": "composer/ca-bundle", @@ -124,6 +124,179 @@ ], "time": "2016-08-30T16:08:34+00:00" }, + { + "name": "graze/data-structure", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/graze/data-structure.git", + "reference": "24e0544b7828f65b1b93ce69ad702c9efb4a64d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graze/data-structure/zipball/24e0544b7828f65b1b93ce69ad702c9efb4a64d0", + "reference": "24e0544b7828f65b1b93ce69ad702c9efb4a64d0", + "shasum": "" + }, + "require": { + "graze/sort": "~2.0", + "php": ">=5.5|^7.0" + }, + "require-dev": { + "graze/standards": "^2.0", + "phpunit/phpunit": "^4.2 | ^5.2", + "squizlabs/php_codesniffer": "^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Graze\\DataStructure\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graze tech team", + "homepage": "https://github.com/graze/data-structure/graphs/contributors" + } + ], + "description": "Data collections and containers", + "homepage": "https://github.com/graze/data-structure", + "keywords": [ + "array", + "collection", + "container", + "data", + "filter", + "map", + "parameters", + "reduce", + "structure" + ], + "time": "2017-11-29T09:06:31+00:00" + }, + { + "name": "graze/parallel-process", + "version": "0.8.1", + "source": { + "type": "git", + "url": "https://github.com/graze/parallel-process.git", + "reference": "84ccec5d8a62a8ed928dc5e1c0da8f646bcef7cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graze/parallel-process/zipball/84ccec5d8a62a8ed928dc5e1c0da8f646bcef7cc", + "reference": "84ccec5d8a62a8ed928dc5e1c0da8f646bcef7cc", + "shasum": "" + }, + "require": { + "graze/data-structure": "^2.0", + "php": "^5.5 | ^7.0", + "psr/log": "^1.0", + "symfony/event-dispatcher": "^2.8 | ^3.2 | ^4.0", + "symfony/process": "^2.8 | ^3.2 | ^4.0" + }, + "require-dev": { + "graze/console-diff-renderer": "^0.6.1", + "graze/standards": "^2", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^5.7.21|^6|^7", + "squizlabs/php_codesniffer": "^3", + "symfony/console": "^3.1 | ^4" + }, + "suggest": { + "graze/console-diff-renderer": "required to use Table and Lines", + "symfony/console": "To use the Table to print current runs" + }, + "type": "library", + "autoload": { + "psr-4": { + "Graze\\ParallelProcess\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Harry Bragg", + "email": "harry.bragg@graze.com", + "role": "Developer" + }, + { + "name": "Graze Developers", + "email": "developers@graze.com", + "homepage": "http://www.graze.com", + "role": "Development Team" + } + ], + "description": "run a pool of processes simultaneously", + "homepage": "https://github.com/graze/parallel-process", + "keywords": [ + "graze", + "parallel-process" + ], + "time": "2018-09-25T09:06:26+00:00" + }, + { + "name": "graze/sort", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/graze/sort.git", + "reference": "50f0896363f177f68be248d7bad9eb0c2f7f666c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/graze/sort/zipball/50f0896363f177f68be248d7bad9eb0c2f7f666c", + "reference": "50f0896363f177f68be248d7bad9eb0c2f7f666c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "adlawson/timezone": "~1.0", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/fn.php" + ], + "psr-4": { + "Graze\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graze tech team", + "homepage": "https://github.com/graze/sort/graphs/contributors" + } + ], + "description": "A collection of array sorting transforms and functions", + "homepage": "https://github.com/graze/sort", + "keywords": [ + "array", + "collection", + "list", + "order", + "ordered", + "schwartzian", + "sort", + "sorting", + "transform" + ], + "time": "2014-09-23T17:01:23+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "6.3.3", diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 826bbca1..27f76aa1 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -2,6 +2,7 @@ namespace Phabalicious\Command; +use Graze\ParallelProcess\Pool; use http\Exception\InvalidArgumentException; use Phabalicious\Configuration\ConfigurationService; use Phabalicious\Configuration\HostConfig; @@ -12,15 +13,19 @@ use Phabalicious\Exception\ValidationFailedException; use Phabalicious\Exception\MissingHostConfigException; use Phabalicious\ShellProvider\ShellProviderInterface; +use Phabalicious\Utilities\ParallelExecutor; use Phabalicious\Validation\ValidationErrorBag; use Phabalicious\Validation\ValidationService; use Psr\Log\NullLogger; use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; abstract class BaseCommand extends BaseOptionsCommand @@ -55,8 +60,15 @@ protected function configure() 'variants', null, InputOption::VALUE_OPTIONAL, - 'Runt the command on a given set of variants simultanously', + 'Run the command on a given set of blueprints simultanously', null + ) + ->addOption( + 'force', + null, + InputOption::VALUE_OPTIONAL, + 'Don\'t ask for confirmation', + false ); parent::configure(); @@ -113,7 +125,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->hostConfig->shell()->setOutput($output); } - if ($input->hasOption('variants')) { + if ($input->getOption('variants')) { return $this->handleVariants($input->getOption('variants'), $input, $output); } } catch (MissingHostConfigException $e) { @@ -205,10 +217,11 @@ protected function startInteractiveShell(ShellProviderInterface $shell, array $c * @param $variants * @param InputInterface $input * @param OutputInterface $output - * @throws ValidationFailedException */ private function handleVariants($variants, InputInterface $input, OutputInterface $output) { + global $argv; + $available_variants = $this->configuration->getBlueprints()->getVariants($this->hostConfig['configName']); if (!$available_variants) { throw new \InvalidArgumentException(sprintf( @@ -232,5 +245,40 @@ private function handleVariants($variants, InputInterface $input, OutputInterfac )); } } + if (!empty($variants)) { + $cmd_lines = []; + $rows = []; + foreach ($variants as $v) { + $cmd = []; + $cmd[] = 'phab'; + + foreach ($input->getArguments() as $a) { + $cmd[] = $a; + } + foreach ($input->getOptions() as $name => $value) { + if ($value && !in_array($name, ['variants', 'blueprint', 'fabfile'])) { + $cmd[] = '--' . $name; + $cmd[]= $value; + } + } + $cmd[] = '--fabfile'; + $cmd[] = $this->configuration->getFabfileLocation(); + $cmd[] = '--blueprint'; + $cmd[] = $v; + + $cmd_lines[] = $cmd; + $rows[] = [$v, implode(' ', $cmd)]; + } + + $style = new SymfonyStyle($input, $output); + $style->table(['variant', 'command'], $rows); + + if ($input->getOption('force') || $style->confirm('Do you want to run these commands? ', false)) { + $executor = new ParallelExecutor($cmd_lines, $output); + return $executor->execute($input, $output); + } + + return 1; + } } } diff --git a/src/Command/InstallCommand.php b/src/Command/InstallCommand.php index f130b593..2aa91868 100644 --- a/src/Command/InstallCommand.php +++ b/src/Command/InstallCommand.php @@ -25,13 +25,6 @@ protected function configure() 'Skip the reset-task if set to true', false ); - $this->addOption( - 'yes', - 'y', - InputOption::VALUE_OPTIONAL, - 'Skip confirmation step, install without question', - false - ); } /** @@ -61,7 +54,9 @@ protected function execute(InputInterface $input, OutputInterface $output) throw new \InvalidArgumentException('This configuration disallows installs!'); } - if (!$input->getOption('yes')) { + $context = new TaskContext($this, $input, $output); + + if (!$input->getOption('force')) { if (!$context->io()->confirm(sprintf( 'Install new database for configuration `%s`?', $this->getHostConfig()['configName'] diff --git a/src/Configuration/ConfigurationService.php b/src/Configuration/ConfigurationService.php index 8781b836..97d73844 100644 --- a/src/Configuration/ConfigurationService.php +++ b/src/Configuration/ConfigurationService.php @@ -35,6 +35,7 @@ class ConfigurationService private $methods; private $fabfilePath; + private $fabfileLocation; private $dockerHosts; private $hosts; @@ -104,6 +105,7 @@ public function readConfiguration(string $path, string $override = ''): bool } $this->setFabfilePath(dirname($fabfile)); + $this->fabfileLocation = $fabfile; $data = $this->readFile($fabfile); if (!$data) { @@ -232,6 +234,11 @@ public function getFabfilePath() return $this->fabfilePath; } + public function getFabfileLocation() + { + return $this->fabfileLocation; + } + public function mergeData(array $data, array $override_data): array { return Utilities::mergeData($data, $override_data); diff --git a/src/Utilities/ParallelExecutor.php b/src/Utilities/ParallelExecutor.php new file mode 100644 index 00000000..92f99536 --- /dev/null +++ b/src/Utilities/ParallelExecutor.php @@ -0,0 +1,77 @@ +pool = new PriorityPool(); + $this->pool->setMaxSimultaneous($max_simultaneous_processes); + + foreach ($command_lines as $cmd) { + $this->add(new ParallelExecutorRun( + $cmd, + $output->section() + )); + } + } + + public function execute(InputInterface $input, ConsoleOutputInterface $output) + { + + $progress_section = $output->section(); + $progress = new ProgressBar($progress_section, $this->pool->count()); + + $this->pool->start(); + + $interval = (1000000); + $current = 0; + $previous = 0; + while ($this->pool->poll()) { + usleep($interval); + $p = $this->pool->getProgress(); + $current = $p[0]; + $progress->advance($current - $previous); + $previous = $current; + } + $progress->finish(); + $style = new SymfonyStyle($input, $output); + + foreach ($this->pool->getAll() as $run) { + if ($run instanceof ParallelExecutorRun) { + $style->section(sprintf('Results of `%s`', $run->getCommandLine())); + $style->writeln($run->getProcess()->getOutput()); + } + } + + return $this->pool->isSuccessful(); + } + + public function add(ParallelExecutorRun $run) + { + $this->pool->add($run); + } + + private function format(RunInterface $run, string $message) + { + if ($run instanceof ProcessRun) { + $cmd = $run->getProcess()->getCommandLine(); + return implode(' ', $cmd) . ': ' . $message; + } + return $message; + } +} diff --git a/src/Utilities/ParallelExecutorRun.php b/src/Utilities/ParallelExecutorRun.php new file mode 100644 index 00000000..84540087 --- /dev/null +++ b/src/Utilities/ParallelExecutorRun.php @@ -0,0 +1,78 @@ +output = $output; + $this->commandLine = implode(' ', $command_line); + + parent::__construct(new Process($command_line)); + $this->addListeners(); + } + + public function addListeners() + { + $this->writeln("Waiting"); + + $this->addListener( + RunEvent::STARTED, + function (RunEvent $event) { + $this->writeln("→ Started"); + } + ); + $this->addListener( + RunEvent::SUCCESSFUL, + function (RunEvent $event) { + $this->writeln("✓ Succeeded"); + } + ); + $this->addListener( + RunEvent::FAILED, + function (RunEvent $event) { + $run = $event->getRun(); + $exceptions = $run->getExceptions(); + $exception = null; + if (count($exceptions) > 0) { + $exception = reset($exceptions); + $error = sprintf( + "x Failed (%d) %s", + $exception->getCode(), + $exception->getMessage() + ); + } else { + $error = "x Failed"; + } + $this->writeln($error); + if ($exception) { + $this->writeln($exception->getMessage()); + } + } + ); + } + + public function writeln($message) + { + $this->output->overwrite($this->commandLine . ': ' . $message); + } + + public function getCommandLine() + { + return $this->commandLine; + } +} diff --git a/symfony.lock b/symfony.lock index 4d61c8d5..7490a874 100644 --- a/symfony.lock +++ b/symfony.lock @@ -8,6 +8,15 @@ "doctrine/instantiator": { "version": "1.1.0" }, + "graze/data-structure": { + "version": "2.1.0" + }, + "graze/parallel-process": { + "version": "0.8.1" + }, + "graze/sort": { + "version": "2.0.1" + }, "guzzlehttp/guzzle": { "version": "6.3.3" }, diff --git a/tests/VariantBaseCommandTest.php b/tests/VariantBaseCommandTest.php new file mode 100644 index 00000000..252a879b --- /dev/null +++ b/tests/VariantBaseCommandTest.php @@ -0,0 +1,88 @@ +application = new Application(); + $this->application->setVersion('3.0.0'); + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + + $configuration = new ConfigurationService($this->application, $logger); + $method_factory = new MethodFactory($configuration, $logger); + $method_factory->addMethod(new FilesMethod($logger)); + $method_factory->addMethod(new ScriptMethod($logger)); + + $configuration->readConfiguration(getcwd() . '/assets/variants-base-command-tests/fabfile.yaml'); + + $this->application->add(new ScriptCommand($configuration, $method_factory)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Could not find variants for `testMissingVariants` in `blueprints` + */ + public function testNoVariants() + { + $command = $this->application->find('script'); + $commandTester = new CommandTester($command); + $commandTester->execute(array( + 'command' => $command->getName(), + '--config' => 'testMissingVariants', + '--variants' => 'all', + )); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Could not find variants `x`, `y`, `z` in `blueprints` + */ + public function testUnavailableVariants() + { + $command = $this->application->find('script'); + $commandTester = new CommandTester($command); + $commandTester->execute(array( + 'command' => $command->getName(), + '--config' => 'test', + '--variants' => 'a,b,c,x,y,z', + )); + } + + + public function testAllVariants() + { + $command = $this->application->find('script'); + $commandTester = new CommandTester($command); + $commandTester->execute(array( + 'command' => $command->getName(), + '--config' => 'test', + '--variants' => 'a,b,c', + 'script' => 'test' + )); + + $output = $commandTester->getDisplay(); + } +} diff --git a/tests/assets/variants-base-command-tests/.fabfile.yaml b/tests/assets/variants-base-command-tests/.fabfile.yaml index 43ba0368..c6a7f476 100644 --- a/tests/assets/variants-base-command-tests/.fabfile.yaml +++ b/tests/assets/variants-base-command-tests/.fabfile.yaml @@ -1,6 +1,6 @@ name: variants-tests -requires: 3.0.0 +requires: 2.0.0 needs: - script @@ -12,10 +12,14 @@ hosts: rootFolder: "%fabfile.path%" test: + scripts: + test: + - echo "%host.configName%" + - sleep 4 blueprint: + inheritsFrom: test configName: test-%slug% - script: - - echo "%host.configName" + From 15e041cf1ea3dbcbdafd2c7d134ded07a9723c88 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Tue, 5 Feb 2019 22:08:55 +0100 Subject: [PATCH 06/17] Implement parallel execution --- src/Command/BaseCommand.php | 11 ----------- src/Utilities/ParallelExecutor.php | 16 +++++++++++----- src/Utilities/ParallelExecutorRun.php | 6 ++++-- tests/VariantBaseCommandTest.php | 10 ++++++++-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index 27f76aa1..ea10b278 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -2,29 +2,18 @@ namespace Phabalicious\Command; -use Graze\ParallelProcess\Pool; -use http\Exception\InvalidArgumentException; use Phabalicious\Configuration\ConfigurationService; use Phabalicious\Configuration\HostConfig; -use Phabalicious\Exception\BlueprintTemplateNotFoundException; -use Phabalicious\Exception\FabfileNotFoundException; -use Phabalicious\Exception\FabfileNotReadableException; -use Phabalicious\Exception\MismatchedVersionException; use Phabalicious\Exception\ValidationFailedException; use Phabalicious\Exception\MissingHostConfigException; use Phabalicious\ShellProvider\ShellProviderInterface; use Phabalicious\Utilities\ParallelExecutor; -use Phabalicious\Validation\ValidationErrorBag; -use Phabalicious\Validation\ValidationService; use Psr\Log\NullLogger; -use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; -use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; diff --git a/src/Utilities/ParallelExecutor.php b/src/Utilities/ParallelExecutor.php index 92f99536..e216195f 100644 --- a/src/Utilities/ParallelExecutor.php +++ b/src/Utilities/ParallelExecutor.php @@ -9,6 +9,8 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\ConsoleSectionOutput; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class ParallelExecutor @@ -17,7 +19,7 @@ class ParallelExecutor private $pool; - public function __construct($command_lines, ConsoleOutputInterface $output, $max_simultaneous_processes = 4) + public function __construct($command_lines, OutputInterface $output, $max_simultaneous_processes = 4) { $this->pool = new PriorityPool(); $this->pool->setMaxSimultaneous($max_simultaneous_processes); @@ -25,20 +27,24 @@ public function __construct($command_lines, ConsoleOutputInterface $output, $max foreach ($command_lines as $cmd) { $this->add(new ParallelExecutorRun( $cmd, - $output->section() + $output instanceof ConsoleSectionOutput + ? $output->section() + : null )); } } - public function execute(InputInterface $input, ConsoleOutputInterface $output) + public function execute(InputInterface $input, OutputInterface $output) { - $progress_section = $output->section(); + $progress_section = $output instanceof ConsoleSectionOutput + ? $output->section() + : $output; $progress = new ProgressBar($progress_section, $this->pool->count()); $this->pool->start(); - $interval = (1000000); + $interval = (200000); $current = 0; $previous = 0; while ($this->pool->poll()) { diff --git a/src/Utilities/ParallelExecutorRun.php b/src/Utilities/ParallelExecutorRun.php index 84540087..8c8da50c 100644 --- a/src/Utilities/ParallelExecutorRun.php +++ b/src/Utilities/ParallelExecutorRun.php @@ -17,13 +17,15 @@ class ParallelExecutorRun extends ProcessRun private $output; private $commandLine; - public function __construct($command_line, ConsoleSectionOutput $output) + public function __construct($command_line, ConsoleSectionOutput $output = null) { $this->output = $output; $this->commandLine = implode(' ', $command_line); parent::__construct(new Process($command_line)); - $this->addListeners(); + if ($output) { + $this->addListeners(); + } } public function addListeners() diff --git a/tests/VariantBaseCommandTest.php b/tests/VariantBaseCommandTest.php index 252a879b..43527c43 100644 --- a/tests/VariantBaseCommandTest.php +++ b/tests/VariantBaseCommandTest.php @@ -71,7 +71,9 @@ public function testUnavailableVariants() )); } - + /** + * @group docker + */ public function testAllVariants() { $command = $this->application->find('script'); @@ -79,10 +81,14 @@ public function testAllVariants() $commandTester->execute(array( 'command' => $command->getName(), '--config' => 'test', - '--variants' => 'a,b,c', + '--variants' => 'all', + '--force' => 1, 'script' => 'test' )); $output = $commandTester->getDisplay(); + $this->assertContains('--blueprint a', $output); + $this->assertContains('--blueprint b', $output); + $this->assertContains('--blueprint c', $output); } } From 4e98f9f0c33849e4a53b03b4736288d32b319762 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Tue, 5 Feb 2019 22:10:53 +0100 Subject: [PATCH 07/17] Fix tests --- tests/InstallCommandTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/InstallCommandTest.php b/tests/InstallCommandTest.php index 10e3440d..1a9d628d 100644 --- a/tests/InstallCommandTest.php +++ b/tests/InstallCommandTest.php @@ -41,7 +41,7 @@ public function testSupportsInstallsOnProd() $commandTester->execute(array( 'command' => $command->getName(), '--config' => 'testProd', - '--yes' => true, + '--force' => true, )); // the output of the command in the console @@ -56,7 +56,7 @@ public function testSupportsInstallsOnStage() $commandTester->execute(array( 'command' => $command->getName(), '--config' => 'testStage', - '--yes' => true, + '--force' => true, )); // the output of the command in the console From 451a69906f1d1818b8b45669e8d91cacc8ae6711 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 16:59:37 +0100 Subject: [PATCH 08/17] Remove obsolete env-settings --- .env | 2 -- 1 file changed, 2 deletions(-) diff --git a/.env b/.env index cdc6058a..2148dfc4 100644 --- a/.env +++ b/.env @@ -1,5 +1,3 @@ # This file is a "template" of which env vars need to be defined for your application # Copy this file to .env file for development, create environment variables when deploying to production # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration -SHELL_VERBOSITY=0 -APP_ENV=debug \ No newline at end of file From 7e55182b50c8ed30e82887d660850ba388585b3d Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 18:37:52 +0100 Subject: [PATCH 09/17] Nicer error-reporting, fixes to variants --- src/Command/BaseCommand.php | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Command/BaseCommand.php b/src/Command/BaseCommand.php index ea10b278..f56040ef 100644 --- a/src/Command/BaseCommand.php +++ b/src/Command/BaseCommand.php @@ -89,6 +89,8 @@ public function completeOptionValues($optionName, CompletionContext $context) */ protected function execute(InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + $this->checkAllRequiredOptionsAreNotEmpty($input); $config_name = '' . $input->getOption('config'); @@ -118,13 +120,14 @@ protected function execute(InputInterface $input, OutputInterface $output) return $this->handleVariants($input->getOption('variants'), $input, $output); } } catch (MissingHostConfigException $e) { - $output->writeln('Could not find host-config named `' . $config_name . '`'); + $io->error(sprintf('Could not find host-config named `%s`', $config_name)); return 1; } catch (ValidationFailedException $e) { - $output->writeln('Could not validate config `' . $config_name . '`'); - foreach ($e->getValidationErrors() as $error_msg) { - $output->writeln('' . $error_msg . ''); - } + $io->error(sprintf( + "Could not validate config `%s`\n\n%s", + $config_name, + implode("\n", $e->getValidationErrors()) + )); return 1; } @@ -206,10 +209,18 @@ protected function startInteractiveShell(ShellProviderInterface $shell, array $c * @param $variants * @param InputInterface $input * @param OutputInterface $output + * @return bool|int */ private function handleVariants($variants, InputInterface $input, OutputInterface $output) { global $argv; + $executable = $argv[0]; + if (basename($executable) !== 'phab') { + $executable = 'bin/phab'; + } + if (getenv('PHABALICIOUS_EXECUTABLE')) { + $executable = getenv('PHABALICIOUS_EXECUTABLE'); + } $available_variants = $this->configuration->getBlueprints()->getVariants($this->hostConfig['configName']); if (!$available_variants) { @@ -239,17 +250,18 @@ private function handleVariants($variants, InputInterface $input, OutputInterfac $rows = []; foreach ($variants as $v) { $cmd = []; - $cmd[] = 'phab'; + $cmd[] = $executable; foreach ($input->getArguments() as $a) { $cmd[] = $a; } foreach ($input->getOptions() as $name => $value) { - if ($value && !in_array($name, ['variants', 'blueprint', 'fabfile'])) { + if ($value && !in_array($name, ['verbose', 'variants', 'blueprint', 'fabfile'])) { $cmd[] = '--' . $name; $cmd[]= $value; } } + $cmd[] = '--no-interaction'; $cmd[] = '--fabfile'; $cmd[] = $this->configuration->getFabfileLocation(); $cmd[] = '--blueprint'; @@ -259,10 +271,11 @@ private function handleVariants($variants, InputInterface $input, OutputInterfac $rows[] = [$v, implode(' ', $cmd)]; } - $style = new SymfonyStyle($input, $output); - $style->table(['variant', 'command'], $rows); + $io = new SymfonyStyle($input, $output); + $io->table(['variant', 'command'], $rows); - if ($input->getOption('force') || $style->confirm('Do you want to run these commands? ', false)) { + if ($input->getOption('force') || $io->confirm('Do you want to run these commands? ', false)) { + $io->comment('Running ...'); $executor = new ParallelExecutor($cmd_lines, $output); return $executor->execute($input, $output); } From 30f9667625d180dd529dee95449d2f849cff54d7 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 18:39:15 +0100 Subject: [PATCH 10/17] Better error reporting for parallel running processes --- src/Utilities/ParallelExecutor.php | 20 +++++++++++++++++--- src/Utilities/ParallelExecutorRun.php | 17 +---------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Utilities/ParallelExecutor.php b/src/Utilities/ParallelExecutor.php index e216195f..777624bc 100644 --- a/src/Utilities/ParallelExecutor.php +++ b/src/Utilities/ParallelExecutor.php @@ -7,6 +7,7 @@ use Graze\ParallelProcess\RunInterface; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\ConsoleSectionOutput; @@ -27,7 +28,7 @@ public function __construct($command_lines, OutputInterface $output, $max_simult foreach ($command_lines as $cmd) { $this->add(new ParallelExecutorRun( $cmd, - $output instanceof ConsoleSectionOutput + $output instanceof ConsoleOutput ? $output->section() : null )); @@ -37,12 +38,14 @@ public function __construct($command_lines, OutputInterface $output, $max_simult public function execute(InputInterface $input, OutputInterface $output) { - $progress_section = $output instanceof ConsoleSectionOutput + $progress_section = $output instanceof ConsoleOutput ? $output->section() : $output; $progress = new ProgressBar($progress_section, $this->pool->count()); $this->pool->start(); + $output->writeln(''); + $progress->display(); $interval = (200000); $current = 0; @@ -60,7 +63,18 @@ public function execute(InputInterface $input, OutputInterface $output) foreach ($this->pool->getAll() as $run) { if ($run instanceof ParallelExecutorRun) { $style->section(sprintf('Results of `%s`', $run->getCommandLine())); - $style->writeln($run->getProcess()->getOutput()); + $exceptions = $run->getExceptions(); + + if (count($exceptions) > 0) { + $exception = reset($exceptions); + $style->error(sprintf( + "Execution failed with error %d:\n%s", + $exception->getCode(), + $exception->getMessage() + )); + } else { + $style->writeln($run->getProcess()->getOutput()); + } } } diff --git a/src/Utilities/ParallelExecutorRun.php b/src/Utilities/ParallelExecutorRun.php index 8c8da50c..a4ebdf62 100644 --- a/src/Utilities/ParallelExecutorRun.php +++ b/src/Utilities/ParallelExecutorRun.php @@ -47,23 +47,8 @@ function (RunEvent $event) { $this->addListener( RunEvent::FAILED, function (RunEvent $event) { - $run = $event->getRun(); - $exceptions = $run->getExceptions(); - $exception = null; - if (count($exceptions) > 0) { - $exception = reset($exceptions); - $error = sprintf( - "x Failed (%d) %s", - $exception->getCode(), - $exception->getMessage() - ); - } else { - $error = "x Failed"; - } + $error = "x Failed"; $this->writeln($error); - if ($exception) { - $this->writeln($exception->getMessage()); - } } ); } From 598e649ad418679dd1d51111b014ab7dae076a4a Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 18:44:10 +0100 Subject: [PATCH 11/17] Fix tests --- tests/VariantBaseCommandTest.php | 7 +++++++ tests/assets/variants-base-command-tests/.fabfile.yaml | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/VariantBaseCommandTest.php b/tests/VariantBaseCommandTest.php index 43527c43..c56e0cd9 100644 --- a/tests/VariantBaseCommandTest.php +++ b/tests/VariantBaseCommandTest.php @@ -76,6 +76,9 @@ public function testUnavailableVariants() */ public function testAllVariants() { + $executable = realpath(getcwd() . '/../bin/phab'); + putenv('PHABALICIOUS_EXECUTABLE=' . $executable); + $command = $this->application->find('script'); $commandTester = new CommandTester($command); $commandTester->execute(array( @@ -90,5 +93,9 @@ public function testAllVariants() $this->assertContains('--blueprint a', $output); $this->assertContains('--blueprint b', $output); $this->assertContains('--blueprint c', $output); + + $this->assertContains('XX-test-a-XX', $output); + $this->assertContains('XX-test-b-XX', $output); + $this->assertContains('XX-test-c-XX', $output); } } diff --git a/tests/assets/variants-base-command-tests/.fabfile.yaml b/tests/assets/variants-base-command-tests/.fabfile.yaml index c6a7f476..4013a0e1 100644 --- a/tests/assets/variants-base-command-tests/.fabfile.yaml +++ b/tests/assets/variants-base-command-tests/.fabfile.yaml @@ -14,8 +14,8 @@ hosts: test: scripts: test: - - echo "%host.configName%" - - sleep 4 + - echo "XX-%host.configName%-XX" + - sleep $[ ( $RANDOM % 10 ) + 1 ]s blueprint: inheritsFrom: test configName: test-%slug% From 630002c8eca3a7f18ac850b3216fc63b1d7aab41 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sat, 9 Mar 2019 18:45:08 +0100 Subject: [PATCH 12/17] Fix some warnings --- src/Command/SelfUpdateCommand.php | 10 +++++++--- src/Method/GitMethod.php | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 6467c08a..9a4d2cf8 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -67,7 +67,7 @@ protected static function getUpdater(Application $application, $allow_unstable) private function runSelfUpdate($allow_unstable) { - $update_data = $this->hasUpdate(); + $update_data = $this->hasUpdate($allow_unstable); if (!$update_data) { return false; } @@ -77,11 +77,14 @@ private function runSelfUpdate($allow_unstable) return $result ? $updater->getNewVersion() : false; } - public function hasUpdate() + public function hasUpdate($allow_unstable = false) { try { $version = $this->getApplication()->getVersion(); - $allow_unstable = (stripos($version, 'alpha') !== false) || (stripos($version, 'beta') !== false); + $allow_unstable = + $allow_unstable || + (stripos($version, 'alpha') !== false) || + (stripos($version, 'beta') !== false); $updater = self::getUpdater($this->getApplication(), $allow_unstable); @@ -120,6 +123,7 @@ public static function registerListener(EventDispatcher $dispatcher) && !$event->getCommand()->isHidden() && !$output->isQuiet() && !$command->getConfiguration()->isOffline() && !$input->hasParameterOption(['--offline']) + && !$input->hasParameterOption(['--no-interaction']) ) { if ($version = $command->hasUpdate()) { $style = new SymfonyStyle($input, $output); diff --git a/src/Method/GitMethod.php b/src/Method/GitMethod.php index 8249af16..087ee2dd 100644 --- a/src/Method/GitMethod.php +++ b/src/Method/GitMethod.php @@ -42,7 +42,7 @@ public function getDefaultConfig(ConfigurationService $configuration_service, ar { return [ 'branch' => 'develop', - 'gitRootFolder' => $host_config['rootFolder'], + 'gitRootFolder' => $host_config['rootFolder'] ?? null, 'ignoreSubmodules' => false, 'gitOptions' => $configuration_service->getSetting('gitOptions', []), ]; From 65b4a72501e8916b903191948e79d135cd731001 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sun, 10 Mar 2019 13:12:30 +0100 Subject: [PATCH 13/17] Fix yaml-indentation for output-command --- src/Command/OutputCommand.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Command/OutputCommand.php b/src/Command/OutputCommand.php index f98149a5..7ad3b136 100644 --- a/src/Command/OutputCommand.php +++ b/src/Command/OutputCommand.php @@ -45,19 +45,24 @@ protected function execute(InputInterface $input, OutputInterface $output) $template = $this->getConfiguration()->getBlueprints()->getTemplate($config); $data = $template->expand($blueprint); - $data = ['hosts' => [ + $data = [ $data['configName'] => $data - ]]; + ]; $dumper = new Dumper(2); $io = new SymfonyStyle($input, $output); - $io->title('Output of applied blueprint `' . $config . '`'); - $io->block($dumper->dump($data, 10, 2)); + if ($output->isDecorated()) { + $io->title('Output of applied blueprint `' . $config . '`'); + } + $io->block( + $dumper->dump($data, 10, 2), + null, + null, + '' + ); return 0; } - - -} \ No newline at end of file +} From 417542cffed3f5f70d2735b011bb26725a091e31 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Sun, 10 Mar 2019 13:17:26 +0100 Subject: [PATCH 14/17] Limit output when using phab with pipes --- src/Command/SelfUpdateCommand.php | 4 +++- src/Utilities/Logger.php | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 9a4d2cf8..9c5152d0 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -120,8 +120,10 @@ public static function registerListener(EventDispatcher $dispatcher) $command = $event->getCommand()->getApplication()->find('self-update'); if ($command + && $output->isDecorated() + && !$output->isQuiet() && !$event->getCommand()->isHidden() - && !$output->isQuiet() && !$command->getConfiguration()->isOffline() + && !$command->getConfiguration()->isOffline() && !$input->hasParameterOption(['--offline']) && !$input->hasParameterOption(['--no-interaction']) ) { diff --git a/src/Utilities/Logger.php b/src/Utilities/Logger.php index d8bfb8d1..1ece9ca3 100644 --- a/src/Utilities/Logger.php +++ b/src/Utilities/Logger.php @@ -6,6 +6,7 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\Output; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -24,7 +25,7 @@ class Logger extends ConsoleLogger LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, - LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_VERBOSE, LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, @@ -59,6 +60,10 @@ public function __construct(InputInterface $input, OutputInterface $output) LogLevel::INFO => self::INFO, LogLevel::DEBUG => self::DEBUG, ]; + if (!$output->isDecorated()) { + // For undecorated output warnings will be shown only when using verbose mode. + $this->verbosityLevelMap[LogLevel::WARNING] = OutputInterface::VERBOSITY_VERBOSE; + } parent::__construct( $output, $this->verbosityLevelMap, From c3e350cdfd71756d88ef46203cb73cbfdea89d30 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Mon, 11 Mar 2019 18:41:17 +0100 Subject: [PATCH 15/17] Fix broken shell autocomplete --- src/ShellCompletion/FishShellCompletionDescriptor.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ShellCompletion/FishShellCompletionDescriptor.php b/src/ShellCompletion/FishShellCompletionDescriptor.php index e245c8bb..14105240 100644 --- a/src/ShellCompletion/FishShellCompletionDescriptor.php +++ b/src/ShellCompletion/FishShellCompletionDescriptor.php @@ -86,9 +86,8 @@ protected function describeInputOption(InputOption $option, array $options = arr ); $this->output->writeln( - " -d '" . - $option->getDescription() . - "'" + " -d " . + escapeshellarg($option->getDescription()) ); if ($command instanceof CompletionAwareInterface) { global $argv; From 06d16156f2f060bfe067ea30c4cd1280bc5325af Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Mon, 18 Mar 2019 12:28:09 +0100 Subject: [PATCH 16/17] Document mattermost integration, fixes #29 --- docs/docs/configuration.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index dbd3ba5a..54bbeadb 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -197,6 +197,10 @@ This will print all host configuration for the host `staging`. * `name` contains the name of the docker-container. This is needed to get the IP-address of the particular docker-container when using ssh-tunnels (see above). * for docker-compose-base setups you can provide the `service` instead the name, phabalicious will get the docker name automatically from the service. +### Configuration of the mattermost-method + +* `notifyOn`: a list of all tasks where to send a message to a Mattermost channel. Have a look at the global Mattermost-configuration-example below. + ### dockerHosts `dockerHosts` is similar structured as the `hosts`-entry. It's a keyed lists of hosts containing all necessary information to create a ssh-connection to the host, controlling the docker-instances, and a list of tasks, the user might call via the `docker`-command. See the `docker`-entry for a more birds-eye-view of the concepts. @@ -321,6 +325,37 @@ jira: projectKey: ``` +### mattermost + +Phabalicious can send notifications to a running Mattermost instance. You need to create an incoming web hook in your instance and pass this to your configuration. Here's an example + +``` +mattermost: + username: phabalicious + webhook: https://chat.your.server.tld/hooks/... + Channel: "my-channel" + +hosts: + test: + needs: + - mattermost + notifyOn: + - deploy + - reset +``` + +* `mattermost` contains all global mattermost config. + * `username` the username to post messages as + * `webhook` the address of the web-hook + * `channel` the channel to post the message to +* `notifyOn` is a list of tasks which should send a notification + +You can test the Mattermost config via + +``` +phab notify "hello world" --config +``` + ### other * `deploymentModule` name of the deployment-module the drush-method enables when doing a deploy From 08985b74e3ab28e33d5cb130a8420a7310912f59 Mon Sep 17 00:00:00 2001 From: Stephan Maximilian Huber Date: Mon, 18 Mar 2019 12:30:58 +0100 Subject: [PATCH 17/17] Small doc fix --- docs/docs/available-tasks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/available-tasks.md b/docs/docs/available-tasks.md index 3acab3ca..59506fe2 100644 --- a/docs/docs/available-tasks.md +++ b/docs/docs/available-tasks.md @@ -521,7 +521,8 @@ phab --config= notify This command will send the notification to Mattermosts channel . For a detailed description have a look into the dedicated documentation. **Examples** -* `phab config:mbb notify "hello world" "off-topic": sends `hello world` to `#off-topic` + +* `phab config:mbb notify "hello world" "off-topic"`: sends `hello world` to `#off-topic` ## app:scaffold