├── .gitignore ├── README.md ├── app ├── bootstrap.php └── config.php ├── bin └── workflow ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── Application.php ├── BufferWithTime.php ├── Command │ ├── Build.php │ ├── CommandInterface.php │ ├── ComposerInstall.php │ ├── ComposerRequire.php │ ├── ComposerUpdate.php │ ├── DatabaseDump.php │ ├── Delete.php │ ├── DockerAwareTrait.php │ ├── Down.php │ ├── Exec.php │ ├── GenerateConfig.php │ ├── Magento.php │ ├── MagentoCompile.php │ ├── MagentoConfigure.php │ ├── MagentoFullInstall.php │ ├── MagentoInstall.php │ ├── MagentoModuleDisable.php │ ├── MagentoModuleEnable.php │ ├── MagentoSetupUpgrade.php │ ├── NewProject.php │ ├── NginxReload.php │ ├── Php.php │ ├── Pull.php │ ├── Push.php │ ├── Restart.php │ ├── Sql.php │ ├── Ssh.php │ ├── Stop.php │ ├── Sync.php │ ├── Up.php │ ├── VarnishDisable.php │ ├── VarnishEnable.php │ └── Watch.php ├── CommandLine.php ├── Config │ ├── ConfigGeneratorFactory.php │ ├── ConfigGeneratorInterface.php │ ├── M1ConfigGenerator.php │ └── M2ConfigGenerator.php ├── Files.php ├── Logger.php ├── LoggerInterface.php ├── NewProject │ ├── Details.php │ ├── DetailsGatherer.php │ ├── Step │ │ ├── AuthJson.php │ │ ├── Capistrano.php │ │ ├── CircleCI.php │ │ ├── ComposerJson.php │ │ ├── CreateProject.php │ │ ├── Docker.php │ │ ├── GitClean.php │ │ ├── GitCommit.php │ │ ├── GitInit.php │ │ ├── PhpStorm.php │ │ ├── PrTemplate.php │ │ ├── Readme.php │ │ └── StepInterface.php │ ├── StepRunner.php │ └── TemplateWriter.php ├── NullLogger.php ├── Platform.php ├── ProcessFailedException.php ├── Test │ ├── Constraint │ │ ├── FileExistsInContainer.php │ │ └── FileUserAndGroupInContainer.php │ └── WorkflowTest.php ├── WatchFactory.php └── functions.php ├── templates ├── capistrano │ ├── Capfile │ ├── Gemfile │ ├── deploy.rb │ └── dev.rb ├── circle │ └── circle.yml ├── composer │ └── auth.json ├── config │ ├── M1 │ │ ├── local.xml │ │ └── local.xml.template │ └── M2 │ │ └── env.php.template ├── docker │ ├── .dockerignore │ ├── certs │ │ └── cert0.pem │ ├── docker-compose.dev.yml │ ├── docker-compose.prod.yml │ ├── docker-compose.yml │ ├── env │ │ ├── local.env.dist │ │ └── production.env.dist │ ├── nginx │ │ └── site.conf │ └── php │ │ ├── Dockerfile │ │ ├── bin │ │ ├── docker-configure │ │ ├── magento-configure │ │ └── magento-install │ │ └── etc │ │ ├── custom.template │ │ ├── msmtprc.template │ │ └── xdebug.template ├── git │ └── .gitignore ├── phpstorm │ └── .phpstorm.meta.php ├── pr │ └── PULL_REQUEST_TEMPLATE.md └── readme │ └── README.md └── test ├── ApplicationTest.php ├── Command ├── AbstractTestCommand.php ├── BuildTest.php ├── ComposerInstallTest.php ├── ComposerRequireTest.php ├── ComposerUpdateTest.php ├── DatabaseDumpTest.php ├── DeleteTest.php ├── DockerAwareTraitTest.php ├── ExecTest.php ├── GenerateConfigTest.php ├── MagentoCompileTest.php ├── MagentoConfigureTest.php ├── MagentoFullInstallTest.php ├── MagentoInstallTest.php ├── MagentoModuleDisableTest.php ├── MagentoModuleEnableTest.php ├── MagentoSetupUpgradeTest.php ├── MagentoTest.php ├── NginxReloadTest.php ├── PhpTest.php ├── PullTest.php ├── PushTest.php ├── RestartTest.php ├── SqlTest.php ├── SshTest.php ├── StopTest.php ├── SyncTest.php ├── UpTest.php ├── VarnishDisableTest.php ├── VarnishEnableTest.php └── WatchTest.php ├── CommandLineTest.php ├── FilesTest.php ├── LoggerTest.php ├── Test ├── Constraint │ ├── FileExistsInContainerTest.php │ └── FileGroupAndOwnerInContainerTest.php └── WorkflowTestTest.php ├── WatchFactoryTest.php └── fixtures ├── broken-env ├── .docker │ └── local.env ├── app.php.dockerfile ├── docker-compose.dev.yml └── docker-compose.yml ├── config ├── env.default.php ├── env.production.php ├── env.queue.php └── local.xml ├── invalid-env ├── .docker │ └── local.env.dist ├── app.php.dockerfile ├── docker-compose.dev.yml └── docker-compose.yml ├── missing-docker-files └── some-file ├── test-env-files └── some-file.php ├── test-env ├── app.php.dockerfile └── docker-compose.yml └── valid-env ├── .docker └── local.env ├── app.php.dockerfile ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── some-file.txt ├── some-folder └── .gitkeep └── some-import.sql /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

JH Development Workflow Tool

2 | 3 |

4 | 5 | Some Badges would be nice. 6 | 7 |

8 | 9 | ## Install 10 | 11 | Make sure you have `fswatch` installed. You can install via homebrew: 12 | 13 | ``` 14 | brew install fswatch 15 | ``` 16 | 17 | Run the following commands: 18 | 19 | ``` 20 | composer global config repositories.workflow vcs git@github.com:wearejh/workflow 21 | composer global require wearejh/workflow:dev-master 22 | ``` 23 | 24 | Notes: 25 | 26 | - Make sure your composer global bin directory `~/.composer/vendor/bin` is available in your `$PATH` environment variable. 27 | - Because packages installed globally with composer share dependencies, you may need to run `composer global update` if the 28 | previous command failed. 29 | 30 | ## Usage 31 | 32 | Before you create any new project, first update the tool, in-case of any fixes or new features. 33 | 34 | ``` 35 | composer global update wearejh/workflow 36 | ``` 37 | 38 | Then run `workflow` to see the list of available commands. 39 | 40 | Read the [wiki](https://github.com/WeareJH/workflow/wiki) for detailed information on each command 41 | 42 | ## Troubleshooting 43 | 44 | If you are experiencing very slow speeds (i.e. it's hanging for minutes inbetween commands), it may be due to a slow DNS lookup to localunixsocket.local. See relevant [GitHub issue](https://github.com/docker/compose/issues/3419#issuecomment-221793401) 45 | A quick fix is to add the following to your hosts file. 46 | 47 | `127.0.0.1 localunixsocket.local` 48 | -------------------------------------------------------------------------------- /app/bootstrap.php: -------------------------------------------------------------------------------- 1 | addDefinitions(__DIR__ . '/config.php') 29 | ->build(); 30 | 31 | exit($c->get(Application::class)->run($c->get(InputInterface::class), $c->get(OutputInterface::class))); 32 | -------------------------------------------------------------------------------- /bin/workflow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 2 | 3 | 4 | 5 | ./test 6 | 7 | 8 | 9 | ./src 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Application extends \Symfony\Component\Console\Application 16 | { 17 | private $fallbackCommand = 'magento'; 18 | 19 | /** 20 | * @param string $commandName 21 | * @return void 22 | */ 23 | public function setFallBackCommand(string $commandName) 24 | { 25 | $this->fallbackCommand = $commandName; 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | public function run(InputInterface $input = null, OutputInterface $output = null) 32 | { 33 | try { 34 | $this->setCatchExceptions(false); 35 | return parent::run($input, $output); 36 | } catch (CommandNotFoundException $e) { 37 | $arguments = $_SERVER['argv']; 38 | array_splice($arguments, 1, 0, $this->fallbackCommand); 39 | 40 | $input = new ArgvInput($arguments); 41 | 42 | try { 43 | return parent::run($input); 44 | } catch (\Exception $e) { 45 | return $this->exception($e, $output); 46 | } 47 | } catch (\Exception $e) { 48 | return $this->exception($e, $output); 49 | } 50 | } 51 | 52 | /** 53 | * @param \Exception $e 54 | * @param OutputInterface|null $output 55 | * @return int|mixed 56 | */ 57 | private function exception(\Exception $e, OutputInterface $output = null) 58 | { 59 | $output = $output ?? new ConsoleOutput; 60 | 61 | if ($output instanceof ConsoleOutputInterface) { 62 | $this->renderException($e, $output->getErrorOutput()); 63 | } else { 64 | $this->renderException($e, $output); 65 | } 66 | 67 | $exitCode = $e->getCode(); 68 | if (is_numeric($exitCode)) { 69 | $exitCode = (int) $exitCode; 70 | if (0 === $exitCode) { 71 | $exitCode = 1; 72 | } 73 | } else { 74 | $exitCode = 1; 75 | } 76 | 77 | return $exitCode; 78 | } 79 | } -------------------------------------------------------------------------------- /src/BufferWithTime.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class BufferWithTime implements OperatorInterface 15 | { 16 | /** 17 | * @var int 18 | */ 19 | private $milliSeconds; 20 | 21 | /** 22 | * @var AsyncSchedulerInterface 23 | */ 24 | private $scheduler; 25 | 26 | /** 27 | * @var array 28 | */ 29 | private $buffer = []; 30 | 31 | private $completed = false; 32 | 33 | public function __construct(int $milliSeconds, AsyncSchedulerInterface $scheduler) 34 | { 35 | $this->milliSeconds = $milliSeconds; 36 | $this->scheduler = $scheduler; 37 | } 38 | 39 | public function __invoke(ObservableInterface $observable, ObserverInterface $observer): DisposableInterface 40 | { 41 | $action = function () use ($observer, &$action) { 42 | 43 | if ($this->completed) { 44 | $observer->onCompleted(); 45 | return; 46 | } 47 | 48 | $observer->onNext($this->buffer); 49 | $this->buffer = []; 50 | $this->scheduler->schedule($action, $this->milliSeconds); 51 | }; 52 | 53 | $this->scheduler->schedule($action, $this->milliSeconds); 54 | return $observable->subscribe( 55 | function ($x) { 56 | $this->buffer[] = $x; 57 | }, 58 | [$observer, 'onError'], 59 | function () { 60 | $this->completed = true; 61 | } 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /src/Command/Build.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Build extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('build') 33 | ->setDescription('Runs docker build to create an image ready for use') 34 | ->addOption('prod', 'p', InputOption::VALUE_NONE, 'Ommits development configurations') 35 | ->addOption('no-cache', null, InputOption::VALUE_NONE, 'Skip the build cache') 36 | ->addOption('service', 's', InputOption::VALUE_REQUIRED, 'Service to build'); 37 | } 38 | 39 | public function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $service = $this->getServiceConfig('php'); 42 | 43 | if (!isset($service['image'])) { 44 | throw new \RuntimeException('No image specified for PHP container'); 45 | } 46 | 47 | $service = $input->getOption('service') ?: 'php'; 48 | $args = $input->getOption('no-cache') ? '--no-cache ' : ''; 49 | 50 | $composeFiles = $this->getComposeFileFlags($input->getOption('prod') ? true : false); 51 | 52 | $this->commandLine->run( 53 | rtrim(sprintf('docker-compose %s build %s%s', $composeFiles, $args, $service)) 54 | ); 55 | 56 | $output->writeln('Build complete!'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Command/CommandInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface CommandInterface 12 | { 13 | public function execute(InputInterface $input, OutputInterface $output); 14 | } 15 | -------------------------------------------------------------------------------- /src/Command/ComposerInstall.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ComposerInstall extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('composer-install') 33 | ->setAliases(['ci']) 34 | ->setDescription('Runs composer install inside the container and pulls back required files to the host'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $container = $this->phpContainerName(); 40 | $flags = ['-o', '--ansi']; 41 | 42 | switch ($output->getVerbosity()) { 43 | case OutputInterface::VERBOSITY_VERBOSE: 44 | $flags[] = '-v'; 45 | break; 46 | case OutputInterface::VERBOSITY_VERY_VERBOSE: 47 | $flags[] = '-vv'; 48 | break; 49 | case OutputInterface::VERBOSITY_DEBUG: 50 | $flags[] = '-vvv'; 51 | break; 52 | } 53 | 54 | $this->commandLine->run( 55 | sprintf( 56 | 'docker exec -u www-data -e COMPOSER_CACHE_DIR=.docker/composer-cache %s composer install %s', 57 | $container, 58 | implode(' ', $flags) 59 | ) 60 | ); 61 | 62 | $pullCommand = $this->getApplication()->find('pull'); 63 | $pullArguments = new ArrayInput(['files' => ['vendor', '.docker/composer-cache']]); 64 | 65 | $pullCommand->run($pullArguments, $output); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Command/ComposerRequire.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ComposerRequire extends Command implements CommandInterface 17 | { 18 | use DockerAwareTrait; 19 | 20 | /** 21 | * @var CommandLine 22 | */ 23 | private $commandLine; 24 | 25 | public function __construct(CommandLine $commandLine) 26 | { 27 | parent::__construct(); 28 | $this->commandLine = $commandLine; 29 | } 30 | 31 | protected function configure() 32 | { 33 | $this 34 | ->setName('composer-require') 35 | ->setAliases(['cr']) 36 | ->addArgument('package', InputArgument::REQUIRED) 37 | ->addOption('dev', 'd', InputOption::VALUE_NONE, 'Require as dev dependency') 38 | ->setDescription('Runs composer require inside the container and pulls back required files to the host'); 39 | } 40 | 41 | public function execute(InputInterface $input, OutputInterface $output) 42 | { 43 | $container = $this->phpContainerName(); 44 | $flags = ['--ansi']; 45 | 46 | switch ($output->getVerbosity()) { 47 | case OutputInterface::VERBOSITY_VERBOSE: 48 | $flags[] = '-v'; 49 | break; 50 | case OutputInterface::VERBOSITY_VERY_VERBOSE: 51 | $flags[] = '-vv'; 52 | break; 53 | case OutputInterface::VERBOSITY_DEBUG: 54 | $flags[] = '-vvv'; 55 | break; 56 | } 57 | 58 | if ($input->getOption('dev')) { 59 | $flags[] = '--dev'; 60 | } 61 | 62 | $command = sprintf( 63 | 'docker exec -u www-data -e COMPOSER_CACHE_DIR=.docker/composer-cache %s composer require %s %s', 64 | $container, 65 | $input->getArgument('package'), 66 | implode(' ', $flags) 67 | ); 68 | $this->commandLine->run($command); 69 | 70 | $pullCommand = $this->getApplication()->find('pull'); 71 | $pullFiles = ['.docker/composer-cache', 'vendor', 'composer.json', 'composer.lock']; 72 | $pullArguments = new ArrayInput(['files' => $pullFiles]); 73 | 74 | $pullCommand->run($pullArguments, $output); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Command/ComposerUpdate.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ComposerUpdate extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('composer-update') 33 | ->setAliases(['cu']) 34 | ->setDescription('Runs composer update inside the container and pulls back required files to the host'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $container = $this->phpContainerName(); 40 | $flags = ['-o', '--ansi']; 41 | 42 | switch ($output->getVerbosity()) { 43 | case OutputInterface::VERBOSITY_VERBOSE: 44 | $flags[] = '-v'; 45 | break; 46 | case OutputInterface::VERBOSITY_VERY_VERBOSE: 47 | $flags[] = '-vv'; 48 | break; 49 | case OutputInterface::VERBOSITY_DEBUG: 50 | $flags[] = '-vvv'; 51 | break; 52 | } 53 | 54 | $this->commandLine->run( 55 | sprintf( 56 | 'docker exec -u www-data -e COMPOSER_CACHE_DIR=.docker/composer-cache %s composer update %s', 57 | $container, 58 | implode(' ', $flags) 59 | ) 60 | ); 61 | 62 | $pullCommand = $this->getApplication()->find('pull'); 63 | $pullArguments = new ArrayInput(['files' => ['.docker/composer-cache', 'vendor', 'composer.lock']]); 64 | 65 | $pullCommand->run($pullArguments, $output); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Command/DatabaseDump.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DatabaseDump extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('db-dump') 33 | ->setDescription('Dump the database to the host') 34 | ->addOption('database', 'd', InputOption::VALUE_REQUIRED, 'Optional database to dump'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $container = $this->getContainerName('db'); 40 | 41 | $this->dump($container, $input); 42 | 43 | $output->writeln('Database dump saved to ./dump.sql'); 44 | } 45 | 46 | private function dump(string $container, InputInterface $input) 47 | { 48 | $details = $this->getDbDetails($input); 49 | $user = $details['user']; 50 | $pass = $details['pass']; 51 | $db = $details['db']; 52 | 53 | $command = sprintf('docker exec -i %s mysqldump -u%s -p%s %s > dump.sql', $container, $user, $pass, $db); 54 | $this->commandLine->runQuietly($command); 55 | } 56 | 57 | private function getDbDetails(InputInterface $input) : array 58 | { 59 | $envVars = $this->getDevEnvironmentVars(); 60 | return [ 61 | 'user' => 'root', 62 | 'pass' => $envVars['MYSQL_ROOT_PASSWORD'] ?? 'docker', 63 | 'db' => $input->getOption('database') ?? $envVars['MYSQL_DATABASE'] ?? 'docker' 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Command/Delete.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Delete extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var Files 21 | */ 22 | private $files; 23 | 24 | public function __construct(Files $files) 25 | { 26 | parent::__construct(); 27 | $this->files = $files; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('delete') 34 | ->setDescription('Delete files from the container') 35 | ->addArgument( 36 | 'files', 37 | InputArgument::REQUIRED | InputArgument::IS_ARRAY, 38 | 'Files to delete, relative to project root' 39 | ); 40 | } 41 | 42 | public function execute(InputInterface $input, OutputInterface $output) 43 | { 44 | $this->files->delete($this->phpContainerName(), (array) $input->getArgument('files')); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Command/DockerAwareTrait.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | trait DockerAwareTrait 13 | { 14 | private function getDevEnvironmentVars(): array 15 | { 16 | $envFile = getcwd() . '/.docker/local.env'; 17 | 18 | if (!file_exists($envFile)) { 19 | throw new \RuntimeException("Local env file doesn't exist, are you sure your configured correctly?"); 20 | } 21 | 22 | return Parser::parse(file_get_contents($envFile)); 23 | } 24 | 25 | private function phpContainerName(): string 26 | { 27 | return $this->getContainerName('php'); 28 | } 29 | 30 | private function getContainerName(string $service): string 31 | { 32 | $serviceConfig = $this->getServiceConfig($service); 33 | 34 | if (!isset($serviceConfig['container_name'])) { 35 | throw new \RuntimeException(sprintf('Unable to get container name for service %s', $service)); 36 | } 37 | 38 | return $serviceConfig['container_name']; 39 | } 40 | 41 | private function getServiceConfig(string $service) : array 42 | { 43 | $cwd = getcwd(); 44 | 45 | $coreComposePath = $cwd . '/docker-compose.yml'; 46 | $devComposePath = $cwd . '/docker-compose.dev.yml'; 47 | 48 | if (!file_exists($coreComposePath)) { 49 | throw new \RuntimeException('Could not locate docker-compose.yml file. Are you in the right directory?'); 50 | } 51 | 52 | try { 53 | $coreYaml = Yaml::parse(file_get_contents($coreComposePath)); 54 | $devYaml = file_exists($devComposePath) ? Yaml::parse(file_get_contents($devComposePath)) : []; 55 | } catch (ParseException $e) { 56 | throw new \RuntimeException(sprintf("Unable to parse docker-compose file \n\n %s", $e->getMessage())); 57 | } 58 | 59 | $yaml = array_merge_recursive($coreYaml, $devYaml); 60 | 61 | if (!isset($yaml['services'][$service])) { 62 | throw new \RuntimeException(sprintf('Service "%s" doesn\'t exist in the compose files', $service)); 63 | } 64 | 65 | return $yaml['services'][$service]; 66 | } 67 | 68 | private function getComposeFileFlags(bool $prod = false) 69 | { 70 | $cwd = getcwd(); 71 | 72 | $devComposePath = $cwd . '/docker-compose.dev.yml'; 73 | $prodComposePath = $cwd . '/docker-compose.prod.yml'; 74 | 75 | $composeFileFlags = '-f docker-compose.yml'; 76 | 77 | if (!$prod && file_exists($devComposePath)) { 78 | $composeFileFlags .= ' -f docker-compose.dev.yml'; 79 | } 80 | 81 | if ($prod && file_exists($prodComposePath)) { 82 | $composeFileFlags .= ' -f docker-compose.prod.yml'; 83 | } 84 | 85 | return $composeFileFlags; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Command/Down.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Down extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('down') 33 | ->setDescription('Stop and remove containers, networks, images, and volumes') 34 | ->addOption('prod', 'p', InputOption::VALUE_OPTIONAL, 'Use when started with --prod / -p'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $composeFiles = $this->getComposeFileFlags($input->getOption('prod') ? true : false); 40 | 41 | $this->commandLine->run(sprintf('docker-compose %s down', $composeFiles)); 42 | 43 | $output->writeln('Containers stopped'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Command/Exec.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Exec extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | protected function configure() 31 | { 32 | $this 33 | ->setName('exec') 34 | ->setDescription('Run an arbitrary command on the app container') 35 | ->addArgument('command-line', InputArgument::REQUIRED, 'Command to execute') 36 | ->addOption( 37 | 'root', 38 | 'r', 39 | InputOption::VALUE_NONE, 40 | 'Exec as root user (must be passed before command e.g workflow exec -r ls -la)' 41 | ) 42 | ->ignoreValidationErrors(); 43 | } 44 | 45 | public function execute(InputInterface $input, OutputInterface $output) 46 | { 47 | $container = $this->phpContainerName(); 48 | $slicePoint = 1 + (int) array_search($this->getName(), $_SERVER['argv'], true); 49 | 50 | $root = false; 51 | if ($_SERVER['argv'][$slicePoint] === '-r' || $_SERVER['argv'][$slicePoint] === '--root') { 52 | $root = true; 53 | $slicePoint++; 54 | } 55 | 56 | $args = array_slice($_SERVER['argv'], $slicePoint); 57 | $user = $root ? 'root' : 'www-data'; 58 | $command = sprintf('docker exec -it -u %s %s %s', $user, $container, implode(' ', $args)); 59 | 60 | $this->commandLine->runInteractively($command); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Command/GenerateConfig.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class GenerateConfig extends Command implements CommandInterface 17 | { 18 | /** 19 | * @var ConfigGeneratorFactory 20 | */ 21 | private $configGeneratorFactory; 22 | 23 | public function __construct(ConfigGeneratorFactory $configGeneratorFactory) 24 | { 25 | parent::__construct(); 26 | $this->configGeneratorFactory = $configGeneratorFactory; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('generate-config') 33 | ->setAliases(['gc']) 34 | ->setDescription('Generate the environment config for your instance, e.g. env.ph or local.xml') 35 | ->addOption('m1', null, InputOption::VALUE_NONE, 'Generate M1 local.xml instead of M2 env.php') 36 | ->addOption( 37 | 'root-dir', 38 | null, 39 | InputOption::VALUE_OPTIONAL, 40 | 'Root dir for write operations, default is CWD', 41 | getcwd() 42 | ); 43 | } 44 | 45 | public function execute(InputInterface $input, OutputInterface $output) 46 | { 47 | $output = new SymfonyStyle($input, $output); 48 | 49 | $platform = $input->getOption('m1') ? Platform::M1() : Platform::M2(); 50 | $rootDir = $input->getOption('root-dir'); 51 | 52 | if (!file_exists($rootDir) || !is_dir($rootDir)) { 53 | throw new \RuntimeException(sprintf('Expected "%s" to be project root directory', $rootDir)); 54 | } 55 | 56 | $this->configGeneratorFactory->create($platform)->generateEnvironmentConfig($rootDir, $output); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Command/Magento.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Magento extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('magento') 33 | ->setAliases(['mage', 'm']) 34 | ->setDescription('Works as a proxy to the Magento bin inside the container') 35 | ->addArgument('cmd', InputArgument::OPTIONAL, 'Magento command you want to run') 36 | ->ignoreValidationErrors(); 37 | } 38 | 39 | public function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $container = $this->phpContainerName(); 42 | $slicePoint = 1 + (int) array_search($this->getName(), $_SERVER['argv'], true); 43 | $args = array_slice($_SERVER['argv'], $slicePoint); 44 | $command = sprintf('docker exec -u www-data %s bin/magento --ansi', $container); 45 | 46 | if (count($args) > 0) { 47 | $command .= sprintf(' %s', implode(' ', $args)); 48 | } 49 | 50 | $this->commandLine->run($command); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/MagentoCompile.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class MagentoCompile extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('magento-compile') 33 | ->setDescription('Runs the magento DI compile command and pulls back required files to the host'); 34 | } 35 | 36 | public function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $container = $this->phpContainerName(); 39 | 40 | $this->commandLine->run(sprintf('docker exec -u www-data %s bin/magento setup:di:compile --ansi', $container)); 41 | 42 | $pullCommand = $this->getApplication()->find('pull'); 43 | $pullArguments = new ArrayInput(['files' => ['var/di', 'var/generation']]); 44 | 45 | $pullCommand->run($pullArguments, $output); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Command/MagentoConfigure.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoConfigure extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('magento-configure') 34 | ->setAliases(['mc']) 35 | ->setDescription('Configures Magento ready for Docker use') 36 | ->addOption('prod', 'p', InputOption::VALUE_NONE, 'Ommits development configurations'); 37 | } 38 | 39 | public function execute(InputInterface $input, OutputInterface $output) 40 | { 41 | $phpContainer = $this->phpContainerName(); 42 | $mailContainer = $this->getContainerName('mail'); 43 | 44 | $this->commandLine->run( 45 | sprintf( 46 | 'docker exec -u www-data %s magento-configure%s', 47 | $phpContainer, 48 | $input->getOption('prod') ? ' -p' : '' 49 | ) 50 | ); 51 | 52 | $pullCommand = $this->getApplication()->find('pull'); 53 | $pullArguments = new ArrayInput(['files' => ['app/etc/env.php']]); 54 | 55 | $pullCommand->run($pullArguments, $output); 56 | 57 | if (!$input->getOption('prod')) { 58 | $this->configureMail($mailContainer, $output); 59 | } 60 | 61 | $output->writeln('Configuration complete!'); 62 | } 63 | 64 | private function configureMail($mailContainer, $output) 65 | { 66 | $sql = "DELETE FROM core_config_data WHERE path LIKE 'system/smtp/%'; "; 67 | $sql .= "INSERT INTO core_config_data (scope, scope_id, path, value) "; 68 | $sql .= "VALUES "; 69 | $sql .= "('default', 0, 'system/smtp/host', '$mailContainer'), "; 70 | $sql .= "('default', 0, 'system/smtp/port', '1025');"; 71 | 72 | $sqlCommand = $this->getApplication()->find('sql'); 73 | $sqlArguments = new ArrayInput(['--sql' => $sql]); 74 | 75 | $sqlCommand->run($sqlArguments, $output); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Command/MagentoFullInstall.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class MagentoFullInstall extends Command implements CommandInterface 14 | { 15 | use DockerAwareTrait; 16 | 17 | public function configure() 18 | { 19 | $this 20 | ->setName('magento-full-install') 21 | ->setAliases(['mfi']) 22 | ->setDescription('Runs magento-install and magento-configure commands') 23 | ->addOption('prod', 'p', InputOption::VALUE_OPTIONAL, 'Ommits development configurations'); 24 | } 25 | 26 | public function execute(InputInterface $input, OutputInterface $output) 27 | { 28 | $installCommand = $this->getApplication()->find('magento-install'); 29 | $configureCommand = $this->getApplication()->find('magento-configure'); 30 | 31 | $installCommand->run($input, $output); 32 | $configureCommand->run($input, $output); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Command/MagentoInstall.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class MagentoInstall extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('magento-install') 33 | ->setAliases(['mi']) 34 | ->setDescription('Runs the magento install script'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $this->commandLine->run(sprintf('docker exec -u www-data %s magento-install', $this->phpContainerName())); 40 | 41 | $pullCommand = $this->getApplication()->find('pull'); 42 | $pullArguments = new ArrayInput(['files' => ['app/etc']]); 43 | 44 | $pullCommand->run($pullArguments, $output); 45 | 46 | $output->writeln('Install complete!'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Command/MagentoModuleDisable.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoModuleDisable extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('module:disable') 34 | ->setDescription('Disable Magento module and updates the config.php file') 35 | ->addArgument('module', InputArgument::REQUIRED, 'Module to disable'); 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $container = $this->phpContainerName(); 41 | $module = $input->getArgument('module'); 42 | 43 | $this->commandLine->run( 44 | sprintf('docker exec -u www-data %s bin/magento module:disable %s --ansi', $container, $module) 45 | ); 46 | 47 | $pullCommand = $this->getApplication()->find('pull'); 48 | $pullArguments = new ArrayInput(['files' => ['app/etc/config.php']]); 49 | 50 | $pullCommand->run($pullArguments, $output); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/MagentoModuleEnable.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoModuleEnable extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('module:enable') 34 | ->setDescription('Enable Magento module and updates the config.php file') 35 | ->addArgument('module', InputArgument::REQUIRED, 'Module to enable'); 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $container = $this->phpContainerName(); 41 | $module = $input->getArgument('module'); 42 | 43 | $this->commandLine->run( 44 | sprintf('docker exec -u www-data %s bin/magento module:enable %s --ansi', $container, $module) 45 | ); 46 | 47 | $pullCommand = $this->getApplication()->find('pull'); 48 | $pullArguments = new ArrayInput(['files' => ['app/etc/config.php']]); 49 | 50 | $pullCommand->run($pullArguments, $output); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/MagentoSetupUpgrade.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class MagentoSetupUpgrade extends Command implements CommandInterface 18 | { 19 | use DockerAwareTrait; 20 | 21 | /** 22 | * Option to skip deletion of generated/code directory 23 | */ 24 | const INPUT_KEY_KEEP_GENERATED = 'keep-generated'; 25 | 26 | /** 27 | * @var CommandLine 28 | */ 29 | private $commandLine; 30 | 31 | public function __construct(CommandLine $commandLine) 32 | { 33 | parent::__construct(); 34 | $this->commandLine = $commandLine; 35 | } 36 | 37 | public function configure() 38 | { 39 | $options = [ 40 | new InputOption( 41 | self::INPUT_KEY_KEEP_GENERATED, 42 | null, 43 | InputOption::VALUE_NONE, 44 | 'Prevents generated files from being deleted. ' . PHP_EOL . 45 | 'We discourage using this option except when deploying to production. ' . PHP_EOL . 46 | 'Consult your system integrator or administrator for more information.' 47 | ) 48 | ]; 49 | 50 | $this 51 | ->setName('setup:upgrade') 52 | ->setDescription('Upgrades the Magento application and updates the config.php file') 53 | ->setDefinition($options); 54 | } 55 | 56 | public function execute(InputInterface $input, OutputInterface $output) 57 | { 58 | $container = $this->phpContainerName(); 59 | $option = $input->getOption(self::INPUT_KEY_KEEP_GENERATED) 60 | ? '--' . self::INPUT_KEY_KEEP_GENERATED 61 | : '' 62 | ; 63 | 64 | $this->commandLine->run( 65 | sprintf('docker exec -u www-data %s bin/magento setup:upgrade %s --ansi', $container, $option) 66 | ); 67 | 68 | $pullCommand = $this->getApplication()->find('pull'); 69 | $pullArguments = new ArrayInput(['files' => ['app/etc/config.php']]); 70 | 71 | $pullCommand->run($pullArguments, $output); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/Command/NewProject.php: -------------------------------------------------------------------------------- 1 | wearejh.com> 15 | * @author Michael Woodward 16 | */ 17 | class NewProject extends Command implements CommandInterface 18 | { 19 | /** 20 | * @var StepRunner 21 | */ 22 | private $stepRunner; 23 | 24 | /** 25 | * @var DetailsGatherer 26 | */ 27 | private $detailsGatherer; 28 | 29 | public function __construct(DetailsGatherer $detailsGatherer, StepRunner $stepRunner) 30 | { 31 | parent::__construct(); 32 | 33 | $this->detailsGatherer = $detailsGatherer; 34 | $this->stepRunner = $stepRunner; 35 | } 36 | 37 | protected function configure() 38 | { 39 | $this 40 | ->setName('new') 41 | ->setDescription('Create a new Magento 2 project'); 42 | } 43 | 44 | public function execute(InputInterface $input, OutputInterface $output) 45 | { 46 | $output = new SymfonyStyle($input, $output); 47 | 48 | $details = $this->detailsGatherer->gatherDetails($output); 49 | 50 | try { 51 | $this->stepRunner->run($details, $output); 52 | } catch (ProcessFailedException $e) { 53 | throw $e; 54 | } 55 | 56 | $output->success(sprintf('%s Successfully Created', $details->getProjectName())); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Command/NginxReload.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class NginxReload extends Command implements CommandInterface 14 | { 15 | use DockerAwareTrait; 16 | 17 | /** 18 | * @var CommandLine 19 | */ 20 | private $commandLine; 21 | 22 | public function __construct(CommandLine $commandLine) 23 | { 24 | parent::__construct(); 25 | $this->commandLine = $commandLine; 26 | } 27 | 28 | public function configure() 29 | { 30 | $this 31 | ->setName('nginx-reload') 32 | ->setAliases(['nginx']) 33 | ->setDescription('Sends reload signal to NGINX in the container'); 34 | } 35 | 36 | public function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $this->commandLine->run(sprintf('docker exec %s nginx -s "reload"', $this->getContainerName('nginx'))); 39 | 40 | $output->writeln('Reload signal sent'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Command/Php.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Php extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('php') 33 | ->setDescription('Run a php script on the app container') 34 | ->addArgument('php-file', InputOption::VALUE_REQUIRED, 'Path to PHP file'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $this->commandLine->runInteractively( 40 | sprintf( 41 | 'docker exec -it -u www-data %s php %s', 42 | $this->phpContainerName(), 43 | $input->getArgument('php-file') 44 | ) 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Command/Pull.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Pull extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var Files 21 | */ 22 | private $files; 23 | 24 | public function __construct(Files $files) 25 | { 26 | parent::__construct(); 27 | $this->files = $files; 28 | } 29 | 30 | public function configure() 31 | { 32 | $help = "Pull files from the docker environment to the host, Useful for pulling vendor etc\n\n"; 33 | $help .= 'If the watch is running and you pull a file that is being watched it will '; 34 | $help .= "automatically be pushed back into the container\n"; 35 | $help .= 'If this is not what you want (large dirs can cause issues here) stop the watch, '; 36 | $help .= 'pull then start the watch again afterwards'; 37 | 38 | $this 39 | ->setName('pull') 40 | ->setDescription('Pull files from the docker environment to the host') 41 | ->setHelp($help) 42 | ->addArgument( 43 | 'files', 44 | InputArgument::REQUIRED | InputArgument::IS_ARRAY, 45 | 'Files to pull, relative to project root' 46 | ) 47 | ->addOption('no-overwrite', 'o', InputOption::VALUE_NONE); 48 | } 49 | 50 | public function execute(InputInterface $input, OutputInterface $output) 51 | { 52 | $overwrite = !$input->getOption('no-overwrite'); 53 | $container = $this->phpContainerName(); 54 | $files = (array) $input->getArgument('files'); 55 | 56 | foreach ($files as $file) { 57 | 58 | if (!$this->files->existsInContainer($container, $file)) { 59 | $output->writeln(sprintf('Looks like "%s" doesn\'t exist', $file)); 60 | return; 61 | } 62 | 63 | if ($overwrite && is_dir($file)) { 64 | //we only remove if the file exists and is a directory 65 | //as the new directory we push may have a different set of files in it 66 | //for files we can just overwrite and save some cycles 67 | $this->files->deleteLocally([$file]); 68 | } 69 | } 70 | 71 | $this->files->download($container, $files); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Command/Push.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Push extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var Files 21 | */ 22 | private $files; 23 | 24 | public function __construct(Files $files) 25 | { 26 | parent::__construct(); 27 | $this->files = $files; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('push') 34 | ->setDescription('Push files from host to the container') 35 | ->addArgument( 36 | 'files', 37 | InputArgument::REQUIRED | InputArgument::IS_ARRAY, 38 | 'Files to push, relative to project root' 39 | ) 40 | ->addOption('no-overwrite', 'o', InputOption::VALUE_NONE); 41 | } 42 | 43 | public function execute(InputInterface $input, OutputInterface $output) 44 | { 45 | $overwrite = !$input->getOption('no-overwrite'); 46 | $container = $this->phpContainerName(); 47 | $files = (array) $input->getArgument('files'); 48 | 49 | 50 | foreach ($files as $file) { 51 | if (!file_exists($file)) { 52 | $output->writeln(sprintf('Looks like "%s" doesn\'t exist', $file)); 53 | return; 54 | } 55 | 56 | if ($overwrite && is_dir($file) && $this->files->existsInContainer($container, $file)) { 57 | //we only remove on container first if it is a directory 58 | //as the new directory we push may have a different set of files in it 59 | //for files we can just overwrite and save some cycles 60 | $this->files->delete($container, [$file]); 61 | } 62 | } 63 | 64 | $this->files->upload($container, $files); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Command/Restart.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Restart extends Command implements CommandInterface 14 | { 15 | public function configure() 16 | { 17 | $this 18 | ->setName('restart') 19 | ->setDescription('Restarts the containers') 20 | ->addOption('prod', 'p', InputOption::VALUE_OPTIONAL, 'Use when started with --prod / -p'); 21 | } 22 | 23 | public function execute(InputInterface $input, OutputInterface $output) 24 | { 25 | $stopCommand = $this->getApplication()->find('stop'); 26 | $upCommand = $this->getApplication()->find('up'); 27 | 28 | $stopCommand->run($input, $output); 29 | $upCommand->run($input, $output); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Command/Ssh.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Ssh extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | protected function configure() 30 | { 31 | $this 32 | ->setName('ssh') 33 | ->setDescription('Open up bash into the app container') 34 | ->addOption('root', 'r', InputOption::VALUE_NONE, 'Open as root user') 35 | ->addOption('container', 'c', InputOption::VALUE_REQUIRED, 'Container to SSH into'); 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $container = $input->getOption('container') 41 | ? $this->getContainerName($input->getOption('container')) 42 | : $this->phpContainerName(); 43 | 44 | $user = $input->getOption('root') ? 'root' : 'www-data'; 45 | 46 | $width = $this->commandLine->runQuietly('tput cols'); 47 | $height = $this->commandLine->runQuietly('tput lines'); 48 | 49 | $command = <<commandLine->runInteractively($command); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/Stop.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Stop extends Command implements CommandInterface 15 | { 16 | use DockerAwareTrait; 17 | 18 | /** 19 | * @var CommandLine 20 | */ 21 | private $commandLine; 22 | 23 | public function __construct(CommandLine $commandLine) 24 | { 25 | parent::__construct(); 26 | $this->commandLine = $commandLine; 27 | } 28 | 29 | public function configure() 30 | { 31 | $this 32 | ->setName('stop') 33 | ->setDescription('Stops the containers running') 34 | ->addOption('prod', 'p', InputOption::VALUE_OPTIONAL, 'Use when started with --prod / -p'); 35 | } 36 | 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | $composeFiles = $this->getComposeFileFlags($input->getOption('prod') ? true : false); 40 | 41 | $this->commandLine->run(sprintf('docker-compose %s stop', $composeFiles)); 42 | 43 | $output->writeln('Containers stopped'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Command/Sync.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Sync extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('sync') 34 | ->setDescription('Syncs changes from the host filesystem to the relevant docker containers') 35 | ->addArgument('file', InputArgument::REQUIRED, 'The changed file path'); 36 | } 37 | 38 | public function execute(InputInterface $input, OutputInterface $output) 39 | { 40 | $path = $input->getArgument('file'); 41 | $containerPath = ltrim(str_replace(getcwd(), '', $path), '/'); 42 | $container = $this->phpContainerName(); 43 | 44 | if (file_exists($path)) { 45 | $pushCommand = $this->getApplication()->find('push'); 46 | $pushArguments = new ArrayInput(['files' => [$path]]); 47 | 48 | $pushCommand->run($pushArguments, $output); 49 | return; 50 | } 51 | 52 | $this->commandLine->run(sprintf('docker exec %s rm -rf /var/www/%s', $container, $containerPath)); 53 | 54 | $output->writeln(" x $containerPath > $container "); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Command/Up.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Up extends Command implements CommandInterface 16 | { 17 | use DockerAwareTrait; 18 | 19 | /** 20 | * @var CommandLine 21 | */ 22 | private $commandLine; 23 | 24 | public function __construct(CommandLine $commandLine) 25 | { 26 | parent::__construct(); 27 | $this->commandLine = $commandLine; 28 | } 29 | 30 | public function configure() 31 | { 32 | $this 33 | ->setName('up') 34 | ->setAliases(['start']) 35 | ->setDescription('Uses docker-compose to start the containers') 36 | ->addOption('prod', 'p', InputOption::VALUE_OPTIONAL, 'Omits development configurations') 37 | ->addOption('no-build', null, InputOption::VALUE_NONE, 'Prevents running a full build'); 38 | } 39 | 40 | public function execute(InputInterface $input, OutputInterface $output) 41 | { 42 | $buildArg = $input->getOption('no-build') ? '' : '--build'; 43 | 44 | $composeFiles = $this->getComposeFileFlags($input->getOption('prod') ? true : false); 45 | 46 | $this->commandLine->run(rtrim(sprintf('docker-compose %s up -d %s', $composeFiles, $buildArg))); 47 | 48 | // Pull composer cache for future builds 49 | $pullCommand = $this->getApplication()->find('pull'); 50 | $pullCommand->run(new ArrayInput(['files' => ['.docker/composer-cache']]), $output); 51 | 52 | $output->writeln('Containers started'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Command/VarnishDisable.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class VarnishDisable extends Command implements CommandInterface 14 | { 15 | use DockerAwareTrait; 16 | 17 | /** 18 | * @var CommandLine 19 | */ 20 | private $commandLine; 21 | 22 | public function __construct(CommandLine $commandLine) 23 | { 24 | parent::__construct(); 25 | $this->commandLine = $commandLine; 26 | } 27 | 28 | public function configure() 29 | { 30 | $this 31 | ->setName('varnish-disable') 32 | ->setAliases(['vd']) 33 | ->setDescription('Switches the VCL to be a proxy'); 34 | } 35 | 36 | public function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $this->commandLine->run('docker-compose exec -T varnish varnishadm vcl.use boot'); 39 | 40 | $output->writeln('Varnish caching disabled'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Command/VarnishEnable.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class VarnishEnable extends Command implements CommandInterface 14 | { 15 | use DockerAwareTrait; 16 | 17 | /** 18 | * @var CommandLine 19 | */ 20 | private $commandLine; 21 | 22 | public function __construct(CommandLine $commandLine) 23 | { 24 | parent::__construct(); 25 | $this->commandLine = $commandLine; 26 | } 27 | 28 | public function configure() 29 | { 30 | $this 31 | ->setName('varnish-enable') 32 | ->setAliases(['ve']) 33 | ->setDescription('Switches the VCL to use caching'); 34 | } 35 | 36 | public function execute(InputInterface $input, OutputInterface $output) 37 | { 38 | $this->commandLine->run('docker-compose exec -T varnish varnishadm vcl.use boot0'); 39 | 40 | $output->writeln('Varnish caching enabled'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Command/Watch.php: -------------------------------------------------------------------------------- 1 | 17 | * @author Aydin Hassan 18 | */ 19 | class Watch extends Command implements CommandInterface 20 | { 21 | use DockerAwareTrait; 22 | 23 | /** 24 | * @var WatchFactory 25 | */ 26 | private $watchFactory; 27 | 28 | /** 29 | * @var Files 30 | */ 31 | private $files; 32 | 33 | public function __construct(WatchFactory $watchFactory, Files $files) 34 | { 35 | parent::__construct(); 36 | $this->watchFactory = $watchFactory; 37 | $this->files = $files; 38 | } 39 | 40 | public function configure() 41 | { 42 | $this 43 | ->setName('watch') 44 | ->setDescription('Keeps track of filesystem changes, piping the changes to the sync command') 45 | ->addArgument('watches', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Custom paths to watch') 46 | ->addOption('no-defaults'); 47 | } 48 | 49 | public function execute(InputInterface $input, OutputInterface $output) 50 | { 51 | $watches = ['app/code', 'app/design','app/i18n', 'composer.json', 'phpcs.xml', 'phpunit.xml']; 52 | $excludes = ['".*__jb_.*$"', '".*swp$"', '".*swpx$"']; 53 | 54 | $watches = $input->getOption('no-defaults') 55 | ? $input->getArgument('watches') 56 | : array_merge($input->getArgument('watches'), $watches); 57 | 58 | if (!$watches) { 59 | throw new \InvalidArgumentException('You must watch at least something...'); 60 | } 61 | 62 | $output->writeln('Watching for file changes...'); 63 | $output->writeln(''); 64 | 65 | $phpContainer = $this->phpContainerName(); 66 | $fsWatch = $this->watchFactory->create($watches, $excludes); 67 | $fsWatch->lift(function () { 68 | return new BufferWithTime(500, Scheduler::getAsync()); 69 | })->subscribe(function (array $watches) use ($phpContainer) { 70 | $files = collect($watches) 71 | ->reject(function (WatchEvent $event) { 72 | return $event->isDir(); 73 | }) 74 | ->map(function (WatchEvent $event) { 75 | return $event->getFile(); 76 | }); 77 | 78 | list($exists, $removed) = $files->partition(function ($item) { 79 | return file_exists($item); 80 | }); 81 | 82 | if ($removed->isNotEmpty()) { 83 | $this->files->delete($phpContainer, $removed->values()->all()); 84 | } 85 | 86 | if ($exists->isNotEmpty()) { 87 | $this->files->upload($phpContainer, $exists->values()->all()); 88 | } 89 | }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/CommandLine.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CommandLine 16 | { 17 | /** 18 | * @var LoopInterface 19 | */ 20 | private $eventLoop; 21 | 22 | /** 23 | * @var LoggerInterface 24 | */ 25 | private $logger; 26 | 27 | /** 28 | * @var OutputInterface 29 | */ 30 | private $output; 31 | 32 | public function __construct(LoopInterface $eventLoop, LoggerInterface $logger, OutputInterface $output) 33 | { 34 | $this->eventLoop = $eventLoop; 35 | $this->logger = $logger; 36 | $this->output = $output; 37 | } 38 | 39 | public function run(string $command) : string 40 | { 41 | $this->logCommand($command, 'normal'); 42 | 43 | return $this->runProcess($this->newProcess($command), $this->onOutput()); 44 | } 45 | 46 | public function runQuietly(string $command) : string 47 | { 48 | $this->logCommand($command, 'quiet'); 49 | 50 | return $this->runProcess($this->newProcess($command)); 51 | } 52 | 53 | public function runInteractively(string $command) : string 54 | { 55 | $this->logCommand($command, 'interactive'); 56 | 57 | $process = $this->newProcess($command); 58 | $process->setTty(true); 59 | 60 | return $this->runProcess($process, $this->onOutput()); 61 | } 62 | 63 | private function newProcess(string $command) : Process 64 | { 65 | $process = new Process($command); 66 | $process->setTimeout(null); 67 | return $process; 68 | } 69 | 70 | private function runProcess(Process $process, $onOutput = null) : string 71 | { 72 | $exitCode = $process->run($onOutput); 73 | 74 | if ($exitCode > 0) { 75 | throw new ProcessFailedException($process->getErrorOutput()); 76 | } 77 | 78 | return $process->getOutput(); 79 | } 80 | 81 | private function onOutput() : callable 82 | { 83 | return function ($type, $buffer) { 84 | $this->output->write($buffer); 85 | }; 86 | } 87 | 88 | public function runAsync(string $command, callable $onComplete = null) 89 | { 90 | $this->logCommand($command, 'async'); 91 | 92 | $errorSubject = new Subject; 93 | $errorSubject->subscribe(function (\Exception $e) { 94 | throw new ProcessFailedException($e->getMessage()); 95 | }); 96 | 97 | $process = new ProcessSubject($command, $errorSubject, null, [], [], $this->eventLoop); 98 | $process->subscribe( 99 | function ($buffer) { 100 | $this->output->write($buffer); 101 | }, 102 | null, 103 | $onComplete 104 | ); 105 | } 106 | 107 | private function logCommand(string $command, string $type) 108 | { 109 | $this->logger->logCommand($command, $type); 110 | } 111 | 112 | public function commandExists(string $executable) : bool 113 | { 114 | return (bool) (new ExecutableFinder)->find($executable, false); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Config/ConfigGeneratorFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ConfigGeneratorFactory 11 | { 12 | /** 13 | * @var M1ConfigGenerator 14 | */ 15 | private $m1ConfigGenerator; 16 | 17 | /** 18 | * @var M2ConfigGenerator 19 | */ 20 | private $m2ConfigGenerator; 21 | 22 | public function __construct(M1ConfigGenerator $m1ConfigGenerator, M2ConfigGenerator $m2ConfigGenerator) 23 | { 24 | $this->m1ConfigGenerator = $m1ConfigGenerator; 25 | $this->m2ConfigGenerator = $m2ConfigGenerator; 26 | } 27 | 28 | public function create(Platform $platform) : ConfigGeneratorInterface 29 | { 30 | switch ($platform) { 31 | case Platform::M1(): 32 | return $this->m1ConfigGenerator; 33 | break; 34 | case Platform::M2(): 35 | default: 36 | return $this->m2ConfigGenerator; 37 | break; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Config/ConfigGeneratorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ConfigGeneratorInterface 11 | { 12 | /** 13 | * @param string $rootDir Root directory for Platform installation 14 | * @param SymfonyStyle $output 15 | * @return void 16 | */ 17 | public function generateEnvironmentConfig(string $rootDir, SymfonyStyle $output); 18 | } 19 | -------------------------------------------------------------------------------- /src/Config/M1ConfigGenerator.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class M1ConfigGenerator implements ConfigGeneratorInterface 10 | { 11 | public function generateEnvironmentConfig(string $rootDir, SymfonyStyle $output) 12 | { 13 | $outputPath = file_exists($rootDir . '/htdocs') 14 | ? $rootDir . '/htdocs/app/etc/local.xml' 15 | : $rootDir . '/app/etc/local.xml'; 16 | 17 | if (!file_exists(dirname($outputPath)) && !mkdir(dirname($outputPath), 0777, true)) { 18 | throw new \RuntimeException(sprintf('Unable to create path "%s"', dirname($outputPath))); 19 | } 20 | 21 | $config = file_get_contents(__DIR__ . '/../../templates/config/M1/local.xml.template'); 22 | file_put_contents($outputPath, $config); 23 | 24 | $output->success('Fresh configuration written to ' . $outputPath); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Config/M2ConfigGenerator.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class M2ConfigGenerator implements ConfigGeneratorInterface 12 | { 13 | /** 14 | * @param string $rootDir 15 | * @param SymfonyStyle $output 16 | * @throws \RuntimeException When unable to output path 17 | */ 18 | public function generateEnvironmentConfig(string $rootDir, SymfonyStyle $output) 19 | { 20 | $outputPath = $rootDir . '/app/etc/env.php'; 21 | 22 | if (!file_exists(dirname($outputPath)) && !mkdir(dirname($outputPath), 0777, true)) { 23 | throw new \RuntimeException(sprintf('Unable to create path "%s"', dirname($outputPath))); 24 | } 25 | 26 | $standardConfig = file_get_contents(__DIR__ . '/../../templates/config/M2/env.php.template'); 27 | 28 | $mode = $output->choice('Which Magento deployment mode ?', ['developer', 'production'], 'developer'); 29 | $queues = $output->confirm('Do you require queue (e.g. RabbitMQ) configuration ?', false); 30 | 31 | $config = str_replace( 32 | ['{mage-mode}', '{use-rabbit}'], 33 | [$mode, $queues ? '' : '#'], 34 | $standardConfig 35 | ); 36 | 37 | file_put_contents($outputPath, $config); 38 | 39 | $output->success('Fresh configuration written to ' . $outputPath); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class Logger extends AbstractLogger implements LoggerInterface 12 | { 13 | /** 14 | * @var string 15 | */ 16 | private $logFile; 17 | 18 | /** 19 | * @var OutputInterface 20 | */ 21 | private $output; 22 | 23 | public function __construct(OutputInterface $output) 24 | { 25 | $this->logFile = getenv('HOME') . '/workflow.log'; 26 | $this->init(); 27 | $this->output = $output; 28 | } 29 | 30 | public function log($level, $message, array $context = []) 31 | { 32 | $dateTime = (new \DateTime)->format('d-m-y H:i:s'); 33 | $line = sprintf("%s (%s) %s\n", $dateTime, strtoupper($level), $message); 34 | file_put_contents( 35 | $this->logFile, 36 | $line, 37 | FILE_APPEND | LOCK_EX 38 | ); 39 | } 40 | 41 | public function logCommand(string $command, string $type) 42 | { 43 | $this->debug(sprintf('Executing command [%s]: "%s"', $type, $command)); 44 | 45 | $this->output->writeln( 46 | sprintf( 47 | 'Executing [%s] %s', 48 | $type, 49 | $command 50 | ) 51 | ); 52 | } 53 | 54 | private function init() 55 | { 56 | @mkdir(dirname($this->logFile), 0777, true); 57 | } 58 | 59 | /** 60 | * For testing 61 | */ 62 | public function setLogFile(string $logFile) 63 | { 64 | $this->logFile = $logFile; 65 | $this->init(); 66 | } 67 | } -------------------------------------------------------------------------------- /src/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface LoggerInterface extends \Psr\Log\LoggerInterface 9 | { 10 | public function logCommand(string $command, string $type); 11 | } -------------------------------------------------------------------------------- /src/NewProject/Details.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class Details 9 | { 10 | private $repo; 11 | private $projectName; 12 | private $namespace; 13 | private $version; 14 | private $pubKey; 15 | private $privKey; 16 | private $accessToken; 17 | private $rabbitMQ; 18 | 19 | public function __construct( 20 | string $repo, 21 | string $projectName, 22 | string $namespace, 23 | string $version, 24 | string $pubKey, 25 | string $privKey, 26 | string $accessToken, 27 | bool $rabbitMQ 28 | ) { 29 | $this->repo = $repo; 30 | $this->projectName = $projectName; 31 | $this->namespace = $namespace; 32 | $this->version = $version; 33 | $this->pubKey = $pubKey; 34 | $this->privKey = $privKey; 35 | $this->accessToken = $accessToken; 36 | $this->rabbitMQ = $rabbitMQ; 37 | } 38 | 39 | public function getRepo() : string 40 | { 41 | return $this->repo; 42 | } 43 | 44 | public function getProjectName() : string 45 | { 46 | return $this->projectName; 47 | } 48 | 49 | public function getNamespace() : string 50 | { 51 | return $this->namespace; 52 | } 53 | 54 | public function getVersion() : string 55 | { 56 | return $this->version; 57 | } 58 | 59 | public function getPubKey() : string 60 | { 61 | return $this->pubKey; 62 | } 63 | 64 | public function getPrivKey() : string 65 | { 66 | return $this->privKey; 67 | } 68 | 69 | public function getProjectDomain() : string 70 | { 71 | return strtolower($this->projectName) . '.dev'; 72 | } 73 | 74 | public function getAccessToken() : string 75 | { 76 | return $this->accessToken; 77 | } 78 | 79 | public function includeRabbitMQ() : bool 80 | { 81 | return $this->rabbitMQ; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/NewProject/DetailsGatherer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DetailsGatherer 11 | { 12 | private $versionMap = [ 13 | 'EE' => 'enterprise', 14 | 'CE' => 'community', 15 | ]; 16 | 17 | public function gatherDetails(OutputStyle $output) 18 | { 19 | $output->note('Gathering Details..'); 20 | 21 | $repo = $output->ask('GitHub repository? Go create an empty repo first', null, function ($answer) { 22 | if (!preg_match('/^git@github\.com:[A-z]+\/[a-z0-9-]+.git$/', $answer)) { 23 | throw new \RuntimeException('GitHub url looks incorrect. Make sure it\'s the SSH url'); 24 | } 25 | return $answer; 26 | }); 27 | 28 | preg_match('/^git@github\.com:[A-z]+\/([a-z0-9-]+).git$/', $repo, $matches); 29 | 30 | $projectName = $matches[1]; 31 | 32 | if (file_exists($projectName)) { 33 | throw new \RuntimeException(sprintf('folder %s exists already', $projectName)); 34 | } 35 | 36 | $namespace = $output->ask('Project Namespace? Eg: Neom', null, function ($answer) { 37 | if (!preg_match('/^[A-Z][a-z]+$/', $answer)) { 38 | throw new \RuntimeException('Package name must be a valid PHP namespace. Eg: Neom'); 39 | } 40 | return $answer; 41 | }); 42 | 43 | $version = $this->versionMap[$output->choice('Magento edition?', array_keys($this->versionMap), 'CE')]; 44 | 45 | $defaultPub = null; 46 | $defaultPriv = null; 47 | if (file_exists(getenv('HOME') . '/.composer/auth.json')) { 48 | $data = json_decode(file_get_contents(getenv('HOME') . '/.composer/auth.json'), true); 49 | 50 | if (isset($data['http-basic']['repo.magento.com'])) { 51 | $defaultPub = $data['http-basic']['repo.magento.com']['username']; 52 | $defaultPriv = $data['http-basic']['repo.magento.com']['password']; 53 | } 54 | } 55 | 56 | $pubKey = $output->ask('Public auth key?', $defaultPub); 57 | $privKey = $output->ask('Private auth key?', $defaultPriv); 58 | 59 | $accessToken = getenv('GITHUB_ACCESS_TOKEN') ?: $output->ask('GitHub Access Token?'); 60 | 61 | $rabbitMq = $version === $this->versionMap['EE'] 62 | ? $output->confirm('Include Rabbit MQ?') 63 | : false; 64 | 65 | return new Details( 66 | $repo, 67 | $projectName, 68 | $namespace, 69 | $version, 70 | $pubKey, 71 | $privKey, 72 | $accessToken, 73 | $rabbitMq 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/NewProject/Step/AuthJson.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class AuthJson implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success(sprintf('Creating auth.json in %s with provided keys', $details->getProjectName())); 27 | 28 | $this->templateWriter->fillAndWriteTemplate( 29 | $details->getProjectName(), 30 | 'composer/auth.json', 31 | 'auth.json', 32 | [ 33 | 'access-token' => $details->getAccessToken(), 34 | 'pubkey' => $details->getPubKey(), 35 | 'prikey' => $details->getPrivKey(), 36 | ] 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/NewProject/Step/Capistrano.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Capistrano implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Adding capistrano config'); 27 | 28 | $this->templateWriter->copyTemplate($details->getProjectName(), 'capistrano/Capfile', 'Capfile'); 29 | $this->templateWriter->copyTemplate($details->getProjectName(), 'capistrano/Gemfile', 'Gemfile'); 30 | 31 | $this->templateWriter->fillAndWriteTemplate( 32 | $details->getProjectName(), 33 | 'capistrano/deploy.rb', 34 | 'cap/deploy.rb', 35 | [ 36 | 'project-name' => $details->getProjectName() 37 | ] 38 | ); 39 | 40 | $this->templateWriter->fillAndWriteTemplate( 41 | $details->getProjectName(), 42 | 'capistrano/dev.rb', 43 | 'cap/deploy/dev.rb', 44 | [ 45 | 'project-name' => $details->getProjectName() 46 | ] 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/NewProject/Step/CircleCI.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class CircleCI implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Adding Circle CI config'); 27 | $this->templateWriter->copyTemplate($details->getProjectName(), 'circle/circle.yml', 'circle.yml'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NewProject/Step/ComposerJson.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ComposerJson implements StepInterface 16 | { 17 | /** 18 | * @var CommandLine 19 | */ 20 | private $commandLine; 21 | 22 | public function __construct(CommandLine $commandLine) 23 | { 24 | $this->commandLine = $commandLine; 25 | } 26 | 27 | public function run(Details $details, OutputStyle $output) 28 | { 29 | $output->success('Setting up composer.json including CS scripts'); 30 | 31 | $data = json_decode(file_get_contents($details->getProjectName() . '/composer.json'), true); 32 | $csFormat = 'phpcs -s app/code/%s --standard=vendor/wearejh/php-coding-standards/Jh'; 33 | $csFixFormat = 'phpcbf -s app/code/%s --standard=vendor/wearejh/php-coding-standards/Jh'; 34 | 35 | $data['name'] = $details->getProjectName() . '-magento2'; 36 | $data['description'] = 'eCommerce Platform for ' . $details->getProjectName(); 37 | $data['repositories'][] = ['type' => 'vcs', 'url' => 'git@github.com:WeareJH/php-coding-standards.git']; 38 | 39 | $data['scripts'] = [ 40 | 'test' => ['@cs', '@unit-tests'], 41 | 'cs' => sprintf($csFormat, $details->getNamespace()), 42 | 'cs-fix' => sprintf($csFixFormat, $details->getNamespace()), 43 | 'unit-tests' => 'phpunit', 44 | 'coverage' => 'phpunit --coverage-text', 45 | 'bootstrap' => 'composer install -o --prefer-dist --ignore-platform-reqs' 46 | ]; 47 | 48 | $data['require']['php'] = '>=7.0.6 <7.1'; 49 | 50 | $data['require-dev']['wearejh/php-coding-standards'] = 'dev-master'; 51 | $data['require-dev']['wearejh/m2-module-symlink-assets'] = '^1.0'; 52 | $data['require-dev']['squizlabs/php_codesniffer'] = '^3.0'; 53 | $data['require-dev']['phpunit/phpunit'] = '^6.0'; 54 | 55 | file_put_contents( 56 | $details->getProjectName() . '/composer.json', 57 | json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) 58 | ); 59 | 60 | $cwd = getcwd(); 61 | chdir($details->getProjectName()); 62 | 63 | $output->success('Updating composer lock file'); 64 | try { 65 | $command = 'docker run --rm '; 66 | $command .= '-v %s:/root/build -v %s/.composer/cache:/root/.composer/cache '; 67 | $command .= 'wearejh/ci-build-env composer update --prefer-dist -qo'; 68 | 69 | $this->commandLine->run(sprintf($command, getcwd(), getenv('HOME'))); 70 | } catch (ProcessFailedException $e) { 71 | throw new \RuntimeException('Could not update composer lock file'); 72 | } 73 | 74 | chdir($cwd); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/NewProject/Step/CreateProject.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CreateProject implements StepInterface 16 | { 17 | /** 18 | * @var TemplateWriter 19 | */ 20 | private $templateWriter; 21 | 22 | /** 23 | * @var CommandLine 24 | */ 25 | private $commandLine; 26 | 27 | public function __construct(CommandLine $commandLine, TemplateWriter $templateWriter) 28 | { 29 | $this->templateWriter = $templateWriter; 30 | $this->commandLine = $commandLine; 31 | } 32 | 33 | public function run(Details $details, OutputStyle $output) 34 | { 35 | $output->success(sprintf('Running composer create-project into %s', $details->getProjectName())); 36 | 37 | $cmdFormat = 'docker run --rm '; 38 | $cmdFormat .= '-v %s:/root/build -v %s/.composer/cache:/root/.composer/cache '; 39 | $cmdFormat .= 'wearejh/ci-build-env '; 40 | $cmdFormat .= 'composer create-project -q --repository-url=https://%s:%s@repo.magento.com/ '; 41 | $cmdFormat .= 'magento/project-%s-edition %s --prefer-dist'; 42 | 43 | $command = sprintf( 44 | $cmdFormat, 45 | getcwd(), 46 | getenv('HOME'), 47 | $details->getPubKey(), 48 | $details->getPrivKey(), 49 | $details->getVersion(), 50 | $details->getProjectName() 51 | ); 52 | 53 | $this->commandLine->run($command); 54 | 55 | $filesToRemove = [ 56 | '/ISSUE_TEMPLATE.md', 57 | '/nginx.conf.sample', 58 | '/php.ini.sample', 59 | '/package.json.sample', 60 | '/LICENSE.txt', 61 | '/LICENSE_AFL.txt', 62 | '/LICENSE_EE.txt', 63 | '/README_EE.txt', 64 | '/Gruntfile.js.sample', 65 | '/CHANGELOG.md', 66 | '/CONTRIBUTING.md', 67 | '/CHANGELOG.md', 68 | '/.travis.yml', 69 | '/.php_cs', 70 | '/.htaccess.sample', 71 | '/.htaccess', 72 | ]; 73 | 74 | foreach ($filesToRemove as $file) { 75 | $file = $details->getProjectName() . $file; 76 | 77 | if (file_exists($file)) { 78 | unlink($file); 79 | } 80 | } 81 | 82 | $this->templateWriter->copyTemplate($details->getProjectName(), 'git/.gitignore', '.gitignore'); 83 | 84 | $output->success(sprintf('Creating code directory app/code/%s', $details->getNamespace())); 85 | 86 | mkdir($details->getProjectName() . '/app/code/' . $details->getNamespace(), 0777, true); 87 | touch($details->getProjectName() . '/app/code/' . $details->getNamespace() . '/.gitkeep'); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/NewProject/Step/GitClean.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class GitClean implements StepInterface 13 | { 14 | /** 15 | * @var CommandLine 16 | */ 17 | private $commandLine; 18 | 19 | public function __construct(CommandLine $commandLine) 20 | { 21 | $this->commandLine = $commandLine; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Cleaning your host'); 27 | 28 | $cwd = getcwd(); 29 | chdir($details->getProjectName()); 30 | 31 | $this->commandLine->runQuietly('git clean -dfX'); 32 | 33 | chdir($cwd); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NewProject/Step/GitCommit.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class GitCommit implements StepInterface 15 | { 16 | /** 17 | * @var CommandLine 18 | */ 19 | private $commandLine; 20 | 21 | public function __construct(CommandLine $commandLine) 22 | { 23 | $this->commandLine = $commandLine; 24 | } 25 | 26 | public function run(Details $details, OutputStyle $output) 27 | { 28 | $output->success('Commit config and pushing to repo'); 29 | 30 | $cwd = getcwd(); 31 | chdir($details->getProjectName()); 32 | 33 | $command = 'git add .'; 34 | $command .= ' && git commit -m "Project Config"'; 35 | $command .= ' && git push origin master'; 36 | 37 | $this->commandLine->runQuietly($command); 38 | 39 | chdir($cwd); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/NewProject/Step/GitInit.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class GitInit implements StepInterface 15 | { 16 | /** 17 | * @var CommandLine 18 | */ 19 | private $commandLine; 20 | 21 | public function __construct(CommandLine $commandLine) 22 | { 23 | $this->commandLine = $commandLine; 24 | } 25 | 26 | public function run(Details $details, OutputStyle $output) 27 | { 28 | $output->success('Initialising git repo'); 29 | 30 | $cwd = getcwd(); 31 | chdir($details->getProjectName()); 32 | 33 | $command = 'git init'; 34 | $command .= ' && git remote add origin ' . $details->getRepo(); 35 | $command .= ' && git add .'; 36 | $command .= ' && git commit -m "Add magento"'; 37 | 38 | $this->commandLine->run($command); 39 | 40 | chdir($cwd); 41 | 42 | $output->success('Initialised repo'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/NewProject/Step/PhpStorm.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PhpStorm implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Adding PHPStorm meta'); 27 | 28 | $this->templateWriter->copyTemplate( 29 | $details->getProjectName(), 30 | 'phpstorm/.phpstorm.meta.php', 31 | '.phpstorm.meta.php' 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NewProject/Step/PrTemplate.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PrTemplate implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Adding PR template'); 27 | 28 | $this->templateWriter->copyTemplate( 29 | $details->getProjectName(), 30 | 'pr/PULL_REQUEST_TEMPLATE.md', 31 | 'PULL_REQUEST_TEMPLATE.md' 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NewProject/Step/Readme.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Readme implements StepInterface 13 | { 14 | /** 15 | * @var TemplateWriter 16 | */ 17 | private $templateWriter; 18 | 19 | public function __construct(TemplateWriter $templateWriter) 20 | { 21 | $this->templateWriter = $templateWriter; 22 | } 23 | 24 | public function run(Details $details, OutputStyle $output) 25 | { 26 | $output->success('Adding Readme'); 27 | 28 | $this->templateWriter->fillAndWriteTemplate( 29 | $details->getProjectName(), 30 | 'readme/README.md', 31 | 'README.md', 32 | [ 33 | 'project-name' => $details->getProjectName(), 34 | 'project-namespace' => $details->getNamespace(), 35 | 'project-domain' => $details->getProjectDomain(), 36 | 'project-repo' => $details->getRepo() 37 | ] 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NewProject/Step/StepInterface.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | interface StepInterface 12 | { 13 | public function run(Details $details, OutputStyle $output); 14 | } 15 | -------------------------------------------------------------------------------- /src/NewProject/StepRunner.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class StepRunner 12 | { 13 | /** 14 | * @var StepInterface[] 15 | */ 16 | private $steps; 17 | 18 | public function __construct(array $steps) 19 | { 20 | $this->steps = array_filter($steps, function ($step) { 21 | return in_array(StepInterface::class, class_implements($step), true); 22 | }); 23 | } 24 | 25 | public function run(Details $details, OutputStyle $output) 26 | { 27 | foreach ($this->steps as $step) { 28 | $step->run($details, $output); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/NewProject/TemplateWriter.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class TemplateWriter 9 | { 10 | const TEMPLATE_DIR = __DIR__ . '/../../templates/'; 11 | 12 | public function copyTemplate(string $projectName, string $templatePath, string $projectDestPath) 13 | { 14 | copy(static::TEMPLATE_DIR . $templatePath, $projectName . '/' .$projectDestPath); 15 | } 16 | 17 | public function fillAndWriteTemplate( 18 | string $projectName, 19 | string $templateFile, 20 | string $outFile, 21 | array $searchAndReplace 22 | ) { 23 | $contents = file_get_contents(static::TEMPLATE_DIR . $templateFile); 24 | $outFilePath = $projectName . '/' . $outFile; 25 | 26 | foreach ($searchAndReplace as $search => $replace) { 27 | $contents = str_replace('{' . $search . '}', $replace, $contents); 28 | } 29 | 30 | $this->writeContents($outFilePath, $contents); 31 | } 32 | 33 | public function regexFillAndWriteTemplate( 34 | string $projectName, 35 | string $templateFile, 36 | string $outFile, 37 | array $regextAndReplace 38 | ) { 39 | $contents = file_get_contents(static::TEMPLATE_DIR . $templateFile); 40 | $outFilePath = $projectName . '/' . $outFile; 41 | 42 | foreach ($regextAndReplace as $regex => $replace) { 43 | $contents = preg_replace($regex, $replace, $contents); 44 | } 45 | 46 | $this->writeContents($outFilePath, $contents); 47 | } 48 | 49 | private function writeContents(string $path, string $contents) 50 | { 51 | if (!file_exists(dirname($path))) { 52 | mkdir(dirname($path), 0777, true); 53 | } 54 | 55 | file_put_contents($path, $contents); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/NullLogger.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class NullLogger extends \Psr\Log\NullLogger implements LoggerInterface 9 | { 10 | 11 | public function logCommand(string $command, string $type) 12 | { 13 | //noop 14 | } 15 | } -------------------------------------------------------------------------------- /src/Platform.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Platform extends Enum 11 | { 12 | const M1 = 'm1'; 13 | const M2 = 'm2'; 14 | } 15 | -------------------------------------------------------------------------------- /src/ProcessFailedException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class ProcessFailedException extends \RuntimeException 9 | { 10 | 11 | } -------------------------------------------------------------------------------- /src/Test/Constraint/FileExistsInContainer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileExistsInContainer extends Constraint 11 | { 12 | /** 13 | * @var string 14 | */ 15 | private $container; 16 | 17 | public function __construct(string $container) 18 | { 19 | parent::__construct(); 20 | $this->container = $container; 21 | } 22 | 23 | public function matches($other) 24 | { 25 | if (!is_string($other)) { 26 | return false; 27 | } 28 | 29 | try { 30 | $this->exec(sprintf('docker exec %s test -e %s', $this->container, $other)); 31 | } catch (\RuntimeException $e) { 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | private function exec(string $command) 39 | { 40 | exec($command, $output, $exitCode); 41 | 42 | if ($exitCode > 0) { 43 | throw new \RuntimeException('Command failed with exit code: ' . $exitCode); 44 | } 45 | } 46 | 47 | /** 48 | * Returns a string representation of the object. 49 | * 50 | * @return string 51 | */ 52 | public function toString() 53 | { 54 | return 'exists in container ' . $this->container; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Test/Constraint/FileUserAndGroupInContainer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileUserAndGroupInContainer extends Constraint 11 | { 12 | /** 13 | * @var string 14 | */ 15 | private $container; 16 | 17 | /** 18 | * @var string 19 | */ 20 | private $group; 21 | /** 22 | * @var string 23 | */ 24 | private $user; 25 | 26 | public function __construct(string $container, string $group, string $user) 27 | { 28 | parent::__construct(); 29 | $this->container = $container; 30 | $this->group = $group; 31 | $this->user = $user; 32 | } 33 | 34 | public function matches($other) 35 | { 36 | if (!is_string($other)) { 37 | return false; 38 | } 39 | 40 | try { 41 | return sprintf('%s:%s', $this->group, $this->user) === 42 | $this->exec(sprintf('docker exec %s stat -c "%%G:%%U" %s', $this->container, $other)); 43 | } catch (\RuntimeException $e) { 44 | return false; 45 | } 46 | } 47 | 48 | private function exec(string $command) : string 49 | { 50 | exec($command, $output, $exitCode); 51 | 52 | if ($exitCode > 0) { 53 | throw new \RuntimeException('Command failed with exit code: ' . $exitCode); 54 | } 55 | 56 | return trim(implode("\n", $output)); 57 | } 58 | 59 | /** 60 | * Returns a string representation of the object. 61 | * 62 | * @return string 63 | */ 64 | public function toString() 65 | { 66 | return 'has correct group and user in container ' . $this->container; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Test/WorkflowTest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class WorkflowTest extends TestCase 14 | { 15 | 16 | public static function assertFileExistsInContainer(string $filePath, string $container, string $message = '') 17 | { 18 | self::assertThat($filePath, new FileExistsInContainer($container), $message); 19 | } 20 | 21 | public static function assertFileNotExistsInContainer(string $filePath, string $container, string $message = '') 22 | { 23 | self::assertThat($filePath, new LogicalNot(new FileExistsInContainer($container)), $message); 24 | } 25 | 26 | public static function assertFileUserAndGroupInContainer(string $filePath, string $group, string $user, string $container, string $message = '') 27 | { 28 | self::assertThat($filePath, new FileUserAndGroupInContainer($container, $group, $user)); 29 | } 30 | 31 | protected function copyFileInToContainer(string $source, string $destination) 32 | { 33 | $this->exec( 34 | sprintf( 35 | 'docker exec m2-php mkdir -p %s', 36 | dirname($destination) 37 | ) 38 | ); 39 | 40 | $this->exec( 41 | sprintf( 42 | 'docker cp %s m2-php:%s', 43 | $source, 44 | $destination 45 | ) 46 | ); 47 | 48 | self::assertFileExistsInContainer($destination, 'm2-php'); 49 | } 50 | 51 | protected function exec(string $command) 52 | { 53 | exec($command, $output, $exitCode); 54 | 55 | if ($exitCode > 0) { 56 | throw new \RuntimeException('Command failed with exit code: ' . $exitCode); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/WatchFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class WatchFactory 13 | { 14 | /** 15 | * @var LoopInterface 16 | */ 17 | private $loop; 18 | 19 | public function __construct(LoopInterface $loop) 20 | { 21 | $this->loop = $loop; 22 | } 23 | 24 | public function create(array $watches, array $excludes = []) : Observable 25 | { 26 | return new FsWatch( 27 | implode(' ', $watches), 28 | sprintf('-e %s -l 0.5', implode(' -e ', $excludes)), 29 | $this->loop 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 3.4' 4 | gem 'capistrano-composer' -------------------------------------------------------------------------------- /templates/capistrano/dev.rb: -------------------------------------------------------------------------------- 1 | set :stage, :development 2 | set :keep_releases, 1 3 | set :deploy_to, '/microcloud/domains/dev2jh/domains/dev.wearejh.info/___{project-name}' 4 | set :branch, 'develop' 5 | 6 | role :web, %w{www-data@dh1.c309.sonassihosting.com:3022} -------------------------------------------------------------------------------- /templates/circle/circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | branches: 3 | only: 4 | - develop 5 | dependencies: 6 | pre: 7 | - gem install bundler 8 | - bundle install 9 | - sudo apt-get update; sudo apt-get install nmap 10 | cache_directories: 11 | - ~/.composer/cache 12 | machine: 13 | php: 14 | version: 7.0.4 15 | ruby: 16 | version: 2.1.2 17 | test: 18 | override: 19 | - composer test 20 | deployment: 21 | develop: 22 | branch: develop 23 | commands: 24 | - for x in $PORT1 $PORT2 $PORT3; do nmap -Pn --host_timeout 201 --max-retries 0 -p $x 149.86.99.250; done 25 | - cap dev deploy -------------------------------------------------------------------------------- /templates/composer/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "github-oauth": { 3 | "github.com": "{access-token}" 4 | }, 5 | "http-basic": { 6 | "repo.magento.com": { 7 | "username": "{pubkey}", 8 | "password": "{prikey}" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/config/M1/local.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeareJH/workflow/bf4d11820ab8eccf1b8f7a7d168419d58131c5d6/templates/config/M1/local.xml -------------------------------------------------------------------------------- /templates/config/M1/local.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | 26 | 27 | 28 | 29 | Mage_Cache_Backend_Redis 30 | 31 | redis 32 | 6379 33 | 34 | 0 35 | 36 | 0 37 | 1 38 | 10 39 | 0 40 | 1 41 | 1 42 | 20480 43 | gzip 44 | 45 | 46 | 47 | db 48 | 49 | redis 50 | 6379 51 | 2.5 52 | 53 | 1 54 | 2048 55 | gzip 56 | 0 57 | 15 58 | 5 59 | 30 60 | 7200 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /templates/config/M2/env.php.template: -------------------------------------------------------------------------------- 1 | [ 5 | 'frontName' => 'admin', 6 | ], 7 | 'db' => [ 8 | 'connection' => [ 9 | 'indexer' => [ 10 | 'host' => 'db', 11 | 'dbname' => 'docker', 12 | 'username' => 'docker', 13 | 'password' => 'docker', 14 | 'model' => 'mysql4', 15 | 'engine' => 'innodb', 16 | 'initStatements' => 'SET NAMES utf8;', 17 | 'active' => '1', 18 | 'persistent' => NULL, 19 | ], 20 | 'default' => [ 21 | 'host' => 'db', 22 | 'dbname' => 'docker', 23 | 'username' => 'docker', 24 | 'password' => 'docker', 25 | 'model' => 'mysql4', 26 | 'engine' => 'innodb', 27 | 'initStatements' => 'SET NAMES utf8;', 28 | 'active' => '1', 29 | ], 30 | ], 31 | 'table_prefix' => '', 32 | ], 33 | 'resource' => [ 34 | 'default_setup' => [ 35 | 'connection' => 'default', 36 | ], 37 | ], 38 | 'x-frame-options' => 'SAMEORIGIN', 39 | 'MAGE_MODE' => '{mage-mode}', 40 | 'session' => [ 41 | 'save' => 'redis', 42 | 'redis' => [ 43 | 'host' => 'redis', 44 | 'port' => '6379', 45 | 'password' => '', 46 | 'timeout' => '2.5', 47 | 'persistent_identifier' => '', 48 | 'database' => '0', 49 | 'compression_threshold' => '2048', 50 | 'compression_library' => 'gzip', 51 | 'log_level' => '1', 52 | 'max_concurrency' => '6', 53 | 'break_after_frontend' => '5', 54 | 'break_after_adminhtml' => '30', 55 | 'first_lifetime' => '600', 56 | 'bot_first_lifetime' => '60', 57 | 'bot_lifetime' => '7200', 58 | 'disable_locking' => '0', 59 | 'min_lifetime' => '60', 60 | 'max_lifetime' => '2592000', 61 | ], 62 | ], 63 | 'cache_types' => [ 64 | 'config' => 1, 65 | 'layout' => 1, 66 | 'block_html' => 1, 67 | 'collections' => 1, 68 | 'reflection' => 1, 69 | 'db_ddl' => 1, 70 | 'eav' => 1, 71 | 'customer_notification' => 1, 72 | 'config_integration' => 1, 73 | 'config_integration_api' => 1, 74 | 'target_rule' => 1, 75 | 'full_page' => 1, 76 | 'amasty_shopby' => 1, 77 | 'translate' => 1, 78 | 'config_webservice' => 1, 79 | ], 80 | 'install' => [ 81 | 'date' => 'Mon, 05 Mar 2018 11:35:35 +0000', 82 | ], 83 | 'cache' => [ 84 | 'frontend' => [ 85 | 'default' => [ 86 | 'backend' => 'Cm_Cache_Backend_Redis', 87 | 'backend_options' => [ 88 | 'server' => 'redis', 89 | 'port' => '6379', 90 | 'database' => '1', 91 | ], 92 | ], 93 | ], 94 | ], 95 | {use-rabbit} 'queue' => [ 96 | {use-rabbit} 'amqp' => [ 97 | {use-rabbit} 'host' => 'rabbitmq', 98 | {use-rabbit} 'port' => '5672', 99 | {use-rabbit} 'user' => getenv('RABBITMQ_DEFAULT_USER'), 100 | {use-rabbit} 'password' => getenv('RABBITMQ_DEFAULT_PASS'), 101 | {use-rabbit} 'virtualhost' => '/', 102 | {use-rabbit} 'ssl' => '', 103 | {use-rabbit} ], 104 | {use-rabbit} ], 105 | ]; 106 | -------------------------------------------------------------------------------- /templates/docker/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore .docker to prevent it being copied with COPY ./ 2 | .docker 3 | 4 | # Unignore these to allow docker build to copy them in 5 | !.docker/php 6 | !.docker/nginx 7 | !.docker/certs 8 | !.docker/composer-cache 9 | 10 | docker-compose*.yml 11 | .dockerignore 12 | 13 | # Ignore some files we don't need in the containers 14 | pub 15 | dev 16 | phpserver 17 | setup 18 | var 19 | LICENSE*.txt 20 | COPYING.txt 21 | .user.ini 22 | .travis.yml 23 | .php_cs 24 | .htaccess 25 | *.sample 26 | *.md 27 | generated 28 | 29 | # Only take custom code from host 30 | app 31 | !app/code 32 | !app/design 33 | 34 | Gemfile 35 | cap 36 | Capfile 37 | circle.yml 38 | .phpstorm.meta.php 39 | 40 | .git 41 | .gitignore 42 | .idea 43 | vendor 44 | 45 | -------------------------------------------------------------------------------- /templates/docker/certs/cert0.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA2wa+9FzdlSLoiMlHh7h6bTVtN2FC70QGKVGCr7cw2CDHnsnD 3 | TEC0HeDNMRZ9byXU0uRBDAeixSMJK+qhHGoVwqVv63XdH6Vk4M/Nc3fta/rWXNVm 4 | wBJM3k+uA6vQ17CTXmq5N5SOi5Yp2CY77dZ1V6BtihOz/8WeZIXR8P+cQYbxHh4Z 5 | yNLoqISfdmrar71/D6KvoiD7kQSIqsW8w8/ZIBrF7fe7GCA/xefV6N3PSxwbNQEF 6 | BfF+SKs6blfiWl9rmYrZKR8pKc2GvryAR1o+QAH2dnzDsiYU//IMP4+efl4/CNH+ 7 | CnX8S7WCvIlWntrHJ+4J6Rs+CAg+Qj/orgJc5QIDAQABAoIBACiONDd9Jo5h21rQ 8 | ok0QLKMiHGn/uWwiVV27OQ6eRg4O68eMJnxtEqzhnjzzpCA7ig/OsfivRUVpel5G 9 | YLSpNARJq9KWjW45qtcxwyIZV74BwUWJQjBYcyFK8ba+TrpReMgnzMns2QQhtvfO 10 | BJTCfBHQo9fIVDEM/4oveTM9sUo0gZj1LngZwYU7KhwsoK4xGB/c01VtORXN0Cxv 11 | 5xxIRbw88EntSJi8nzr6Jb6ceo6ugqkRUpr3M4nycgzCej0P2w45VYT5cuuYnLCJ 12 | vKh60jtB4+/x6/VIwB85f3E4TisxZ/FurNVTbL/ckpxf3fEdGJ0h8yX8wjxRyyML 13 | SR+v/vUCgYEA9jJTQVx9Y1KaaZVcmXOakGEwTXyazEkG7UjwJ8akY9xEkD+85SHX 14 | xAXsV0u/GUXp8Mzee/ekfq5//c111WWJzwJVb8keEcyHm4rhnuP4K3dIBRlc7oWg 15 | 0iFes4pKLJf+3cYCgMwcE3BWHdU8oxKChZKBiysE15o67CevvE1lbkMCgYEA4790 16 | Br56JsiZcqDjHdbxlQY6dChBQNUgA9gjmsPtoBRARO+Su1alI0K6Ortd7qN6U+6/ 17 | tR6MfJ0vmpCLJ/118FcRjce75gp7Lh2skhALNMShkqVsXv1gi6wZ0ci0vT6Kr+kI 18 | sixkn/FuqF6hLyZt3T8LfV9jtlbDu8buATyUGbcCgYEA42b8i6TYlINZ6ShzDbJA 19 | FBgRO6FaglL5uPbkDHloomx0UCDvY11tJLyr3r4yVy/CtA8nea32HKUlx9Kdgmx1 20 | a+Yl8Ej+I0aeA0e2usKrGcrllQAmXJLFRxJXnNKhTKtgWIxrB3iAflwGzyuFBMM2 21 | GBaI3Xjw0gy9XCAULIP4qm0CgYEAyJRaZIInZLbxZiJKRIKEu8IDgz/c0HOjwZ7/ 22 | JJQAWSbcv5nbugCCaj6fc5CHFuCFoRw5XROtmSZ6wX8h/7NbxrN4M01AsEZ03FWo 23 | Ie/dXrj6sAPfIP24pOqKxtckTzOgw3LShNFSQgdJdSH6hWMsCVo4DVAQqotZ0axO 24 | +2nV6ZsCgYBjiaowVqf/PsnDdGYciezCxVJ2GwHsO47QaNzjLfyVkd9ueNefrIlg 25 | CLE5BBB4BK45ZWuO7Dk0F92ytUZ8Pi2/48fIRn+C+j9oRlmHKLgHiGhnoWLPXDYS 26 | z48hmO/g56d0472FXC5OqBqRVsu8MPXPyxdDoMJIGQL5F5dbfk+yfg== 27 | -----END RSA PRIVATE KEY----- 28 | -----BEGIN CERTIFICATE----- 29 | MIIDWzCCAkOgAwIBAgIJAJc1JICrjnAPMA0GCSqGSIb3DQEBBQUAMCcxCjAIBgNV 30 | BAMUASoxDDAKBgNVBAoTA0pILjELMAkGA1UEBhMCVUswHhcNMTcxMDI1MTczOTQ0 31 | WhcNMjAxMDA5MTczOTQ0WjAnMQowCAYDVQQDFAEqMQwwCgYDVQQKEwNKSC4xCzAJ 32 | BgNVBAYTAlVLMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2wa+9Fzd 33 | lSLoiMlHh7h6bTVtN2FC70QGKVGCr7cw2CDHnsnDTEC0HeDNMRZ9byXU0uRBDAei 34 | xSMJK+qhHGoVwqVv63XdH6Vk4M/Nc3fta/rWXNVmwBJM3k+uA6vQ17CTXmq5N5SO 35 | i5Yp2CY77dZ1V6BtihOz/8WeZIXR8P+cQYbxHh4ZyNLoqISfdmrar71/D6KvoiD7 36 | kQSIqsW8w8/ZIBrF7fe7GCA/xefV6N3PSxwbNQEFBfF+SKs6blfiWl9rmYrZKR8p 37 | Kc2GvryAR1o+QAH2dnzDsiYU//IMP4+efl4/CNH+CnX8S7WCvIlWntrHJ+4J6Rs+ 38 | CAg+Qj/orgJc5QIDAQABo4GJMIGGMB0GA1UdDgQWBBQu2vjcFNPDjsGiIedr0euY 39 | Rw9UxzBXBgNVHSMEUDBOgBQu2vjcFNPDjsGiIedr0euYRw9Ux6ErpCkwJzEKMAgG 40 | A1UEAxQBKjEMMAoGA1UEChMDSkguMQswCQYDVQQGEwJVS4IJAJc1JICrjnAPMAwG 41 | A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAMs7PIfwyOS6iOaDMi8TB8Tj 42 | AYVzndQh10+kaZbmfqSwW5P/WKmj+jIgv7E5BixA3olmMqReZTpLa58AT+Afj+3D 43 | hN4WPC9AXtp3eE3/rYFMzMuV4qpKa1+J9BPvFUX7ezMkABSxpzH1aCu39wvRgLTd 44 | aDenrWOR8FrGQS6q5qfRUEA/73NZDCfjKv9hqNaxp5+5SZpD23U26jbD+iFnv52I 45 | 4iIwlMc3eCpnOx9ZvaScbtYtvinkg2/xBpOQxanasw/48vI+NnGvfp/sjzSVnPo/ 46 | jlV2yR8UWuw9nMdz7oIvK2DLDfoIM76qg75CgzZ0D9rdlSwvhfr/Z3NYQRK+M48= 47 | -----END CERTIFICATE----- 48 | -------------------------------------------------------------------------------- /templates/docker/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx: 5 | volumes: 6 | - .docker/certs:/etc/letsencrypt 7 | env_file: 8 | - ./.docker/local.env 9 | 10 | php: 11 | env_file: 12 | - .docker/local.env 13 | volumes: 14 | - .docker/composer-cache:/var/www/.docker/composer-cache 15 | 16 | db: 17 | env_file: 18 | - ./.docker/local.env 19 | volumes: 20 | - .docker/db/:/docker-entrypoint-initdb.d/ 21 | 22 | {use-rabbit} rabbitmq: 23 | {use-rabbit} env_file: 24 | {use-rabbit} - ./.docker/local.env 25 | 26 | mail: 27 | container_name: {project-name}-mail 28 | image: mailhog/mailhog 29 | ports: 30 | - 1025 31 | - 8025:8025 32 | 33 | blackfire: 34 | container_name: {project-name}-blackfire 35 | image: blackfire/blackfire 36 | env_file: 37 | - .docker/local.env 38 | -------------------------------------------------------------------------------- /templates/docker/docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | app-var: 5 | 6 | services: 7 | nginx: 8 | volumes: 9 | - /etc/letsencrypt:/etc/letsencrypt 10 | env_file: 11 | - .docker/production.env 12 | 13 | php: 14 | env_file: 15 | - .docker/production.env 16 | volumes: 17 | - app-var:/var/www/var 18 | 19 | db: 20 | env_file: 21 | - .docker/production.env 22 | 23 | {use-rabbit} rabbitmq: 24 | {use-rabbit} env_file: 25 | {use-rabbit} - ./.docker/production.env 26 | 27 | # varnish: 28 | # image: million12/varnish 29 | # ports: 30 | # - "80:80" 31 | # env_file: 32 | # - ./.docker/production.env -------------------------------------------------------------------------------- /templates/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | db-data: 5 | app-env: 6 | 7 | services: 8 | haproxy: 9 | image: dockercloud/haproxy:latest 10 | links: 11 | - varnish 12 | environment: 13 | - CERT_FOLDER=/certs 14 | volumes: 15 | - /var/run/docker.sock:/var/run/docker.sock 16 | - .docker/certs:/certs 17 | ports: 18 | - "80:80" 19 | - "443:443" 20 | 21 | varnish: 22 | container_name: {project-name}-varnish 23 | image: wearejh/magento-varnish:latest 24 | environment: 25 | - FORCE_SSL=yes 26 | depends_on: 27 | - nginx 28 | 29 | nginx: 30 | container_name: {project-name} 31 | image: nginx:stable-alpine 32 | working_dir: /var/www 33 | volumes: 34 | - .docker/nginx/sites:/etc/nginx/conf.d 35 | volumes_from: 36 | - php 37 | depends_on: 38 | - php 39 | 40 | php: 41 | container_name: {project-name}-php 42 | image: wearejh/{project-name} 43 | build: 44 | context: . 45 | dockerfile: .docker/php/Dockerfile 46 | volumes: 47 | - app-env:/var/www/app/etc 48 | - ~/.composer/auth.json:/root/.composer/auth.json 49 | depends_on: 50 | - db 51 | ports: 52 | - 9000 53 | 54 | db: 55 | container_name: {project-name}-db 56 | image: mysql:5.6 57 | volumes: 58 | - db-data:/var/lib/mysql 59 | ports: 60 | - "3306:3306" 61 | restart: unless-stopped 62 | 63 | redis: 64 | container_name: {project-name}-redis 65 | image: redis:3-alpine 66 | ports: 67 | - "6379:6379" 68 | 69 | # elasticsearch: 70 | # container_name: {project-name}-elasticsearch 71 | # image: elasticsearch 72 | # ports: 73 | # - "9200:9200" 74 | # - "9300:9300" 75 | 76 | {use-rabbit} rabbitmq: 77 | {use-rabbit} container_name: {project-name}-rabbitmq 78 | {use-rabbit} image: rabbitmq:3.6.1-management 79 | {use-rabbit} ports: 80 | {use-rabbit} - "15672:15672" 81 | {use-rabbit} - "5672:5672" 82 | -------------------------------------------------------------------------------- /templates/docker/env/local.env.dist: -------------------------------------------------------------------------------- 1 | # Copy to local.env and store on local machine 2 | 3 | # Host Config 4 | MAGE_ROOT_DIR=/var/www 5 | MAGE_HOST=https://mage.dev 6 | MAGE_ADMIN_USER=admin 7 | MAGE_ADMIN_PASS=password123 8 | MAGE_ADMIN_FIRSTNAME=Joe 9 | MAGE_ADMIN_LASTNAME=Bloggs 10 | MAGE_ADMIN_EMAIL=magento@wearejh.com 11 | MAGE_BACKEND_FRONTNAME=admin 12 | HTTPS=on 13 | 14 | # MySQL Details 15 | MYSQL_ROOT_PASSWORD=docker 16 | MYSQL_DATABASE=docker 17 | MYSQL_USER=docker 18 | MYSQL_PASSWORD=docker 19 | 20 | # PHP 21 | PHP_MEMORY_LIMIT=2G 22 | 23 | # RabbitMQ 24 | RABBITMQ_DEFAULT_USER=user 25 | RABBITMQ_DEFAULT_PASS=password 26 | 27 | ## Mail config 28 | MAIL_HOST=mail 29 | MAIL_PORT=1025 30 | 31 | ## Xdebug config 32 | XDEBUG_IDE_KEY=PHPSTORM 33 | XDEBUG_CONFIG=remote_host=docker.for.mac.host.internal 34 | XDEBUG_ENABLE=true 35 | PHP_IDE_CONFIG=serverName={project-domain} 36 | 37 | # Blackfire 38 | BLACKFIRE_CLIENT_ID= 39 | BLACKFIRE_CLIENT_TOKEN= 40 | BLACKFIRE_SERVER_ID= 41 | BLACKFIRE_SERVER_TOKEN= 42 | -------------------------------------------------------------------------------- /templates/docker/env/production.env.dist: -------------------------------------------------------------------------------- 1 | # Copy to production.env and store on production machine 2 | # Never commit your production details into the repo 3 | 4 | # Host Config 5 | MAGE_HOST=https://mage.dev 6 | MAGE_ADMIN_USER=admin 7 | MAGE_ADMIN_PASS=password123 8 | MAGE_ADMIN_FIRSTNAME=Joe 9 | MAGE_ADMIN_LASTNAME=Bloggs 10 | MAGE_ADMIN_EMAIL=magento@wearejh.com 11 | MAGE_BACKEND_FRONTNAME=admin 12 | HTTPS=on 13 | 14 | # MySQL Details 15 | MYSQL_ROOT_PASSWORD=docker 16 | MYSQL_DATABASE=docker 17 | MYSQL_USER=docker 18 | MYSQL_PASSWORD=docker 19 | 20 | # PHP 21 | PHP_MEMORY_LIMIT=2G 22 | 23 | # RabbitMQ 24 | RABBITMQ_DEFAULT_USER=user 25 | RABBITMQ_DEFAULT_PASS=password 26 | 27 | ## Xdebug config 28 | XDEBUG_IDE_KEY=PHPSTORM 29 | XDEBUG_CONFIG=remote_host=10.254.254.254 30 | XDEBUG_ENABLE=false -------------------------------------------------------------------------------- /templates/docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Michael Woodward 3 | 4 | ARG BUILD_ENV=dev 5 | ENV PROD_ENV=prod 6 | 7 | RUN apt-get update \ 8 | && apt-get install -y \ 9 | cron \ 10 | libfreetype6-dev \ 11 | libicu-dev \ 12 | libjpeg62-turbo-dev \ 13 | libmcrypt-dev \ 14 | libpng12-dev \ 15 | libxslt1-dev \ 16 | gettext \ 17 | msmtp \ 18 | git \ 19 | vim 20 | 21 | RUN docker-php-ext-configure \ 22 | gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ 23 | 24 | RUN docker-php-ext-install \ 25 | gd \ 26 | intl \ 27 | mbstring \ 28 | mcrypt \ 29 | pdo_mysql \ 30 | xsl \ 31 | zip \ 32 | soap \ 33 | bcmath \ 34 | mysqli \ 35 | opcache \ 36 | pcntl 37 | 38 | # Xdebug 39 | RUN [ "$BUILD_ENV" != "$PROD_ENV" ] && pecl install -o -f xdebug-2.5.0; true 40 | 41 | # Blackfire 42 | RUN [ "$BUILD_ENV" != "$PROD_ENV" ] \ 43 | && version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \ 44 | && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/amd64/$version \ 45 | && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp \ 46 | && mv /tmp/blackfire-*.so $(php -r "echo ini_get('extension_dir');")/blackfire.so \ 47 | && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8707\n" > $PHP_INI_DIR/conf.d/blackfire.ini; \ 48 | true 49 | 50 | # Configuration files 51 | COPY .docker/php/etc/custom.template .docker/php/etc/xdebug.template /usr/local/etc/php/conf.d/ 52 | COPY .docker/php/etc/msmtprc.template /etc/msmtprc.template 53 | 54 | # Copy in Entrypoint file & Magento installation script 55 | COPY .docker/php/bin/docker-configure .docker/php/bin/magento-install .docker/php/bin/magento-configure /usr/local/bin/ 56 | RUN chmod +x /usr/local/bin/docker-configure /usr/local/bin/magento-install /usr/local/bin/magento-configure 57 | 58 | # Composer 59 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 60 | 61 | WORKDIR /var/www 62 | 63 | RUN [ ! -d pub ] && mkdir pub 64 | RUN [ ! -d var ] && mkdir var 65 | RUN [ ! -d app/etc ] && mkdir -p app/etc 66 | 67 | COPY composer.json composer.lock auth.json ./ 68 | COPY .docker/composer-cache .docker/composer-cache 69 | 70 | RUN chsh -s /bin/bash www-data \ 71 | && chown -R www-data:www-data ./ 72 | 73 | RUN [ "$BUILD_ENV" = "$PROD_ENV" ] \ 74 | && su - www-data -c "COMPOSER_CACHE_DIR=.docker/composer-cache composer install --no-dev --no-interaction --prefer-dist -o" \ 75 | || su - www-data -c "COMPOSER_CACHE_DIR=.docker/composer-cache composer install --no-interaction --prefer-dist -o" 76 | 77 | COPY app app 78 | COPY .data-migration .data-migration 79 | 80 | RUN rm -rf \ 81 | html \ 82 | dev \ 83 | phpserver \ 84 | LICENSE*.txt \ 85 | COPYING.txt \ 86 | .user.ini \ 87 | .travis.yml \ 88 | .php_cs \ 89 | .htaccess* \ 90 | *.sample \ 91 | .phpstorm.meta.php \ 92 | *.md 93 | 94 | RUN find . -user root | xargs chown www-data:www-data \ 95 | && chmod +x bin/magento 96 | 97 | VOLUME ["/var/www"] 98 | ENTRYPOINT ["/usr/local/bin/docker-configure"] 99 | CMD ["php-fpm"] 100 | -------------------------------------------------------------------------------- /templates/docker/php/bin/docker-configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | envsubst < "/usr/local/etc/php/conf.d/xdebug.template" > "/usr/local/etc/php/conf.d/xdebug.ini" 4 | envsubst < "/usr/local/etc/php/conf.d/custom.template" > "/usr/local/etc/php/conf.d/custom.ini" 5 | envsubst < "/etc/msmtprc.template" > "/etc/msmtprc" 6 | 7 | rm /usr/local/etc/php/conf.d/xdebug.template 8 | rm /usr/local/etc/php/conf.d/custom.template 9 | rm /etc/msmtprc.template 10 | 11 | [ "$XDEBUG_ENABLE" = "true" ] && \ 12 | docker-php-ext-enable xdebug && \ 13 | echo "Xdebug is enabled" 14 | 15 | exec "$@" 16 | -------------------------------------------------------------------------------- /templates/docker/php/bin/magento-configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'redis', 11 | 'redis' => 12 | [ 13 | 'host' => 'redis', 14 | 'port' => '6379', 15 | 'password' => '', 16 | 'timeout' => '2.5', 17 | 'persistent_identifier' => '', 18 | 'database' => '0', 19 | 'compression_threshold' => '2048', 20 | 'compression_library' => 'gzip', 21 | 'log_level' => '1', 22 | 'max_concurrency' => '6', 23 | 'break_after_frontend' => '5', 24 | 'break_after_adminhtml' => '30', 25 | 'first_lifetime' => '600', 26 | 'bot_first_lifetime' => '60', 27 | 'bot_lifetime' => '7200', 28 | 'disable_locking' => '0', 29 | 'min_lifetime' => '60', 30 | 'max_lifetime' => '2592000' 31 | ] 32 | ]; 33 | 34 | $config['cache'] = [ 35 | 'frontend' => 36 | [ 37 | 'default' => 38 | [ 39 | 'backend' => 'Cm_Cache_Backend_Redis', 40 | 'backend_options' => 41 | [ 42 | 'server' => 'redis', 43 | 'port' => '6379', 44 | 'database' => '1' 45 | ], 46 | ] 47 | ] 48 | ]; 49 | 50 | $config['MAGE_MODE'] = $isProduction ? 'production' : 'developer'; 51 | 52 | $contents = <<exec($sql); 76 | -------------------------------------------------------------------------------- /templates/docker/php/bin/magento-install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /var/www 4 | 5 | echo "Installing Magento..." 6 | 7 | bin/magento setup:install \ 8 | --db-host=db \ 9 | --db-name=$MYSQL_DATABASE \ 10 | --db-user=$MYSQL_USER \ 11 | --db-password=$MYSQL_PASSWORD \ 12 | --base-url=$MAGE_HOST \ 13 | --base-url-secure=$MAGE_HOST \ 14 | --admin-firstname="$MAGE_ADMIN_FIRSTNAME" \ 15 | --admin-lastname="$MAGE_ADMIN_LASTNAME" \ 16 | --admin-email=$MAGE_ADMIN_EMAIL \ 17 | --admin-user=$MAGE_ADMIN_USER \ 18 | --admin-password=$MAGE_ADMIN_PASS \ 19 | --backend-frontname=$MAGE_BACKEND_FRONTNAME \ 20 | --use-secure=1 \ 21 | --use-secure-admin=1 \ ##RABBIT 22 | --amqp-host=rabbitmq \ 23 | --amqp-port=5672 \ 24 | --amqp-user=$RABBITMQ_DEFAULT_USER \ 25 | --amqp-password=$RABBITMQ_DEFAULT_PASS \ 26 | --amqp-virtualhost=/ \ ##RABBIT 27 | --cleanup-database -vvv \ 28 | || { exit 1; } 29 | 30 | bin/magento index:reindex && \ 31 | bin/magento dev:source-theme:deploy --area="adminhtml" --theme="Magento/backend" css/styles-old css/styles && \ 32 | bin/magento dev:source-theme:deploy --theme="Magento/blank" css/styles-m css/styles-l css/email css/email-inline && \ 33 | bin/magento dev:source-theme:deploy && \ 34 | bin/magento setup:static-content:deploy 35 | 36 | echo "Installation complete ᕦ( ̿ ̿ - ̿ ̿ )つ" 37 | -------------------------------------------------------------------------------- /templates/docker/php/etc/custom.template: -------------------------------------------------------------------------------- 1 | memory_limit = ${PHP_MEMORY_LIMIT}; 2 | session.auto_start=off; 3 | suhosin.session.cryptua=off; 4 | sendmail_path=/usr/bin/msmtp -t; 5 | opcache.enable=1 6 | opcache.enable_cli=1 7 | opcache.save_comments=1 8 | opcache.max_accelerated_files=65406 9 | opcache.memory_consumption=256 10 | upload_max_filesize = 20M 11 | post_max_size = 20M -------------------------------------------------------------------------------- /templates/docker/php/etc/msmtprc.template: -------------------------------------------------------------------------------- 1 | account default 2 | host ${MAIL_HOST} 3 | port ${MAIL_PORT} 4 | auto_from on -------------------------------------------------------------------------------- /templates/docker/php/etc/xdebug.template: -------------------------------------------------------------------------------- 1 | xdebug.remote_enable = 1 2 | xdebug.remote_port = 9000 3 | xdebug.scream = 0 4 | xdebug.show_local_vars = 1 5 | xdebug.idekey = ${XDEBUG_IDE_KEY} 6 | xdebug.var_display_max_data = -1 7 | -------------------------------------------------------------------------------- /templates/git/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | vendor 4 | .docker/*.env 5 | .docker/composer-cache/* 6 | 7 | /.idea 8 | /.gitattributes 9 | /app/design/*/Magento 10 | /app/etc 11 | /app/i18n/magento 12 | /app/*.* 13 | 14 | /bin 15 | /update 16 | /dev 17 | /lib 18 | /pub 19 | /setup 20 | /var 21 | /phpserver 22 | /generated 23 | 24 | /*.* 25 | 26 | !/.data-migration 27 | !/.dockerignore 28 | !/docker-compose.* 29 | !/.docker/ 30 | !/composer.json 31 | !/composer.lock 32 | !/README.md 33 | !/.gitignore 34 | !/circle.yml 35 | !/auth.json 36 | !*/**/.gitkeep 37 | !/.phpstorm.meta.php 38 | !PULL_REQUEST_TEMPLATE.md 39 | -------------------------------------------------------------------------------- /templates/phpstorm/.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | [ 6 | "" == "@", 7 | ], 8 | \Magento\Framework\ObjectManagerInterface::get('') => [ 9 | "" == "@", 10 | ], 11 | ]; 12 | } -------------------------------------------------------------------------------- /templates/pr/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **Ready for Merge?** : Yes/No 2 | - **Ticket** : [#244](https://app.activecollab.com/ticket/path) 3 | 4 | ### PR Dependencies 5 | 6 | - List of other PRs that need to be merged prior to this 7 | - Omit section if no dependants 8 | 9 | 10 | ### Feature/Issue Description 11 | 12 | This description should be a small TLDR of the ticket, the reviewer may wish to view the ticket if they need more details other than this. 13 | 14 | ### Code Explanation 15 | 16 | This section should provide details on why you have made the changes and if you feel necessary a justifcation of your approach. 17 | 18 | ### Issues / Caveats 19 | 20 | Use this to warn of any issues you think may arise from this, caveats etc and any issues you came across during the implementation that may help the reviewer better understand or help in some way. 21 | 22 | ### TODOs 23 | 24 | - [x] Checklist of all todos 25 | - [ ] Use if still in WIP 26 | - [ ] Anything with a TODO list should not be merged! 27 | 28 | --- 29 | 30 | ![A nice GIF (of your choice) to amuse your colleague and brighten up their day](https://media.giphy.com/media/d31wZLtbhmGOnH8Y/giphy.gif) -------------------------------------------------------------------------------- /templates/readme/README.md: -------------------------------------------------------------------------------- 1 |

{project-namespace}

2 | 3 |

4 | 5 | Some Badges would be nice. 6 | 7 |

8 | 9 | ## Initial Setup 10 | 11 | Ensure you have [Docker ](https://docs.docker.com/docker-for-mac/) and the [Workflow tool](https://github.com/WeareJH/workflow) installed and it's pre-requisites. 12 | 13 | If you want to supply a database seed, put the SQL file in `.docker/db` before starting. 14 | 15 | ```sh 16 | $ git clone {project-reop} && cd {project-name} 17 | $ cp .docker/local.env.dist .docker/local.env 18 | $ workflow start 19 | ``` 20 | 21 | Your recommended to run two terminal windows as the start command will start a file watcher copying in files to the container on change which you should keep running. 22 | 23 | In a new terminal you can now finish off your setup dependant on your installation type. 24 | 25 | ### Supplied Database Seed 26 | 27 | If you supplied a database seed you simply need to run the configure command. 28 | 29 | ```sh 30 | $ workflow mc 31 | ``` 32 | 33 | ### Fresh Magento Database 34 | 35 | If you want a fresh database and haven't supplied a database seed you need to run a full install. 36 | 37 | ```sh 38 | $ workflow mfi 39 | ``` 40 | 41 | After the initial setup you will be able to use the `workflow` too to start, stop development as and when you wish, dynamic data will stay persistant until you remove it through docker. 42 | 43 | *Note: You can configure the `.docker/local.env` file to meet your requirements but likely not required* 44 | 45 | ## Mailhog 46 | 47 | Mailhog is accessible on `http://{project-domain}:8025/` 48 | 49 | ## Tests 50 | 51 | ```bash 52 | composer test 53 | ``` -------------------------------------------------------------------------------- /test/ApplicationTest.php: -------------------------------------------------------------------------------- 1 | setAutoExit(false); 24 | $app->add(new class extends Command { 25 | protected function configure() 26 | { 27 | $this->setName('some-command'); 28 | } 29 | }); 30 | 31 | $fallback = new Magento(new CommandLine(new StreamSelectLoop, new NullLogger, new ConsoleOutput)); 32 | $fallback->setCode(function (InputInterface $input) { 33 | static::assertTrue($input->hasArgument('cmd')); 34 | static::assertEquals($input->getArgument('cmd'), 'some-command-that-does-not-exist'); 35 | }); 36 | 37 | $app->add($fallback); 38 | 39 | $_SERVER['argv'] = ['workflow', 'some-command-that-does-not-exist', 'arg1']; 40 | 41 | $exitCode = $app->run(); 42 | 43 | static::assertEquals(0, $exitCode); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Command/AbstractTestCommand.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class AbstractTestCommand extends TestCase 21 | { 22 | /** 23 | * @var Prophet 24 | */ 25 | protected $prophet; 26 | 27 | /** 28 | * @var ObjectProphecy|InputInterface 29 | */ 30 | protected $input; 31 | 32 | /** 33 | * @var ObjectProphecy|OutputInterface 34 | */ 35 | protected $output; 36 | 37 | /** 38 | * @var ObjectProphecy|CommandLine 39 | */ 40 | protected $commandLine; 41 | 42 | public function setUp() 43 | { 44 | $this->prophet = new Prophet(); 45 | 46 | $this->input = $this->prophesize(ArgvInput::class); 47 | $this->output = $this->prophesize(Output::class); 48 | 49 | $this->commandLine = $this->prophesize(CommandLine::class); 50 | } 51 | 52 | protected function useInvalidEnvironment() 53 | { 54 | chdir(__DIR__ . '/../fixtures/invalid-env'); 55 | } 56 | 57 | protected function useValidEnvironment() 58 | { 59 | chdir(__DIR__ . '/../fixtures/valid-env'); 60 | } 61 | 62 | protected function useBrokenEnvironment() 63 | { 64 | chdir(__DIR__ . '/../fixtures/broken-env'); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/Command/DatabaseDumpTest.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class DatabaseDumpTest extends AbstractTestCommand 12 | { 13 | /** 14 | * @var DatabaseDump 15 | */ 16 | private $command; 17 | 18 | public function setUp() 19 | { 20 | parent::setUp(); 21 | $this->command = new DatabaseDump($this->commandLine->reveal()); 22 | } 23 | 24 | public function tearDown() 25 | { 26 | $this->prophet->checkPredictions(); 27 | } 28 | 29 | public function testCommandIsConfigured() 30 | { 31 | static::assertEquals('db-dump', $this->command->getName()); 32 | static::assertEquals([], $this->command->getAliases()); 33 | static::assertEquals('Dump the database to the host', $this->command->getDescription()); 34 | } 35 | 36 | public function testHasDatabaseOptionAndValueIsRequired() 37 | { 38 | $definition = $this->command->getDefinition(); 39 | 40 | static::assertTrue($definition->hasOption('database')); 41 | static::assertTrue($definition->getOption('database')->isValueRequired()); 42 | } 43 | 44 | public function testDump() 45 | { 46 | $this->useValidEnvironment(); 47 | 48 | $this->input->getOption('database')->willReturn(null); 49 | 50 | $this->commandLine->runQuietly('docker exec -i m2-db mysqldump -uroot -pdocker docker > dump.sql') 51 | ->shouldBeCalled(); 52 | 53 | $this->output->writeln('Database dump saved to ./dump.sql')->shouldBeCalled(); 54 | 55 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 56 | } 57 | 58 | public function testDumpWithCustomDatabase() 59 | { 60 | $this->useValidEnvironment(); 61 | 62 | $this->input->getOption('database')->willReturn('custom_db'); 63 | 64 | $this->commandLine->runQuietly('docker exec -i m2-db mysqldump -uroot -pdocker custom_db > dump.sql') 65 | ->shouldBeCalled(); 66 | $this->output->writeln('Database dump saved to ./dump.sql')->shouldBeCalled(); 67 | 68 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/Command/DeleteTest.php: -------------------------------------------------------------------------------- 1 | files = $this->prophesize(Files::class); 25 | $this->command = new Delete($this->files->reveal()); 26 | } 27 | 28 | public function tearDown() 29 | { 30 | $this->prophet->checkPredictions(); 31 | } 32 | 33 | public function testCommandIsConfigured() 34 | { 35 | static::assertEquals('delete', $this->command->getName()); 36 | static::assertEquals([], $this->command->getAliases()); 37 | static::assertEquals('Delete files from the container', $this->command->getDescription()); 38 | static::assertArrayHasKey('files', $this->command->getDefinition()->getArguments()); 39 | } 40 | 41 | public function testFilesArgumentIsRequiredAndArray() 42 | { 43 | $args = $this->command->getDefinition()->getArguments(); 44 | /** @var InputArgument $fileArg */ 45 | $fileArg = array_shift($args); 46 | 47 | static::assertTrue($fileArg->isRequired()); 48 | static::assertTrue($fileArg->isArray()); 49 | } 50 | 51 | public function testDeleteCommandRelativePath() 52 | { 53 | $this->useValidEnvironment(); 54 | 55 | $this->input->getArgument('files')->shouldBeCalled()->willReturn(['some-file.txt']); 56 | 57 | $this->files->delete('m2-php', ['some-file.txt'])->shouldBeCalled(); 58 | 59 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 60 | } 61 | 62 | public function testDeleteCommandAbsolutePath() 63 | { 64 | $this->useValidEnvironment(); 65 | 66 | $filePath = realpath('some-file.txt'); 67 | $this->input->getArgument('files')->shouldBeCalled()->willReturn([$filePath]); 68 | 69 | $this->files->delete('m2-php', [$filePath])->shouldBeCalled(); 70 | 71 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 72 | } 73 | 74 | public function testExceptionThrownIfContainerNameNotFound() 75 | { 76 | $this->useInvalidEnvironment(); 77 | $this->expectException(\RuntimeException::class); 78 | 79 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/Command/DockerAwareTraitTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DockerAwareTraitTest extends AbstractTestCommand 11 | { 12 | private $implementation; 13 | 14 | public function setUp() 15 | { 16 | parent::setUp(); 17 | 18 | $this->implementation = new class() 19 | { 20 | use DockerAwareTrait; 21 | 22 | public function getDevEnvironmentVarsTest() 23 | { 24 | return $this->getDevEnvironmentVars(); 25 | } 26 | 27 | public function containerNameTest(string $name) 28 | { 29 | $this->getContainerName($name); 30 | } 31 | }; 32 | } 33 | 34 | public function testExceptionIsThrownIfServiceDoesntExist() 35 | { 36 | $this->useValidEnvironment(); 37 | $this->expectException(\RuntimeException::class); 38 | $this->implementation->containerNameTest('non-existant-service'); 39 | } 40 | 41 | public function testExceptionIsThrownWhenComposeFileUnParsable() 42 | { 43 | $this->useBrokenEnvironment(); 44 | $this->expectException(\RuntimeException::class); 45 | $this->implementation->containerNameTest('php'); 46 | } 47 | 48 | public function testExceptionIsThrownIfLocalEnvFileDoesntExist() 49 | { 50 | $this->useInvalidEnvironment(); 51 | $this->expectException(\RuntimeException::class); 52 | $this->implementation->getDevEnvironmentVarsTest(); 53 | } 54 | 55 | public function testExceptionIsThrownIfLDockerFileDoesntExist() 56 | { 57 | chdir(__DIR__ . '/../fixtures/missing-docker-files'); 58 | $this->expectException(\RuntimeException::class); 59 | 60 | $message = 'Could not locate docker-compose.yml file. Are you in the right directory?'; 61 | 62 | $this->expectExceptionMessage($message); 63 | $this->implementation->containerNameTest('php'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/Command/MagentoCompileTest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class MagentoCompileTest extends AbstractTestCommand 17 | { 18 | /** 19 | * @var ComposerUpdate 20 | */ 21 | private $command; 22 | 23 | /** 24 | * @var ObjectProphecy|Application 25 | */ 26 | private $application; 27 | 28 | /** 29 | * @var ObjectProphecy|Pull 30 | */ 31 | private $pullCommand; 32 | 33 | 34 | public function setUp() 35 | { 36 | parent::setUp(); 37 | 38 | $this->command = new MagentoCompile($this->commandLine->reveal()); 39 | $this->application = $this->prophesize(Application::class); 40 | $this->pullCommand = $this->prophesize(Pull::class); 41 | 42 | $this->application->getHelperSet()->willReturn(new HelperSet); 43 | $this->application->find('pull')->willReturn($this->pullCommand->reveal()); 44 | 45 | $this->command->setApplication($this->application->reveal()); 46 | } 47 | 48 | public function tearDown() 49 | { 50 | $this->prophet->checkPredictions(); 51 | } 52 | 53 | public function testCommandIsConfigured() 54 | { 55 | $description = 'Runs the magento DI compile command and pulls back required files to the host'; 56 | 57 | static::assertEquals('magento-compile', $this->command->getName()); 58 | static::assertEquals($description, $this->command->getDescription()); 59 | } 60 | 61 | public function testComposerInstallCommand() 62 | { 63 | $this->useValidEnvironment(); 64 | 65 | $this->commandLine->run('docker exec -u www-data m2-php bin/magento setup:di:compile --ansi')->shouldBeCalled(); 66 | 67 | $expectedInput = new ArrayInput(['files' => ['var/di', 'var/generation']]); 68 | $this->pullCommand->run($expectedInput, $this->output)->shouldBeCalled(); 69 | 70 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 71 | } 72 | 73 | public function testExceptionThrownIfContainerNameNotFound() 74 | { 75 | $this->useInvalidEnvironment(); 76 | $this->expectException(\RuntimeException::class); 77 | 78 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/Command/MagentoFullInstallTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoFullInstallTest extends AbstractTestCommand 16 | { 17 | /** 18 | * @var MagentoFullInstall 19 | */ 20 | private $command; 21 | 22 | /** 23 | * @var ObjectProphecy|Application 24 | */ 25 | private $application; 26 | 27 | /** 28 | * @var ObjectProphecy|MagentoInstall 29 | */ 30 | private $installCommand; 31 | 32 | /** 33 | * @var ObjectProphecy|MagentoConfigure 34 | */ 35 | private $configureCommand; 36 | 37 | 38 | public function setUp() 39 | { 40 | parent::setUp(); 41 | 42 | $this->command = new MagentoFullInstall(); 43 | $this->application = $this->prophesize(Application::class); 44 | $this->installCommand = $this->prophesize(MagentoInstall::class); 45 | $this->configureCommand = $this->prophesize(MagentoConfigure::class); 46 | 47 | $this->application->getHelperSet()->willReturn(new HelperSet); 48 | $this->application->find('magento-install')->willReturn($this->installCommand->reveal()); 49 | $this->application->find('magento-configure')->willReturn($this->configureCommand->reveal()); 50 | 51 | $this->command->setApplication($this->application->reveal()); 52 | } 53 | 54 | public function tearDown() 55 | { 56 | $this->prophet->checkPredictions(); 57 | } 58 | 59 | public function testCommandIsConfigured() 60 | { 61 | static::assertEquals('magento-full-install', $this->command->getName()); 62 | static::assertEquals(['mfi'], $this->command->getAliases()); 63 | static::assertEquals('Runs magento-install and magento-configure commands', $this->command->getDescription()); 64 | static::assertArrayHasKey('prod', $this->command->getDefinition()->getOptions()); 65 | } 66 | 67 | public function testCommandRunsBothSubCommands() 68 | { 69 | $this->application->find('magento-install')->shouldBeCalled(); 70 | $this->application->find('magento-configure')->shouldBeCalled(); 71 | 72 | $this->installCommand->run($this->input, $this->output)->shouldBeCalled(); 73 | $this->configureCommand->run($this->input, $this->output)->shouldBeCalled(); 74 | 75 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/Command/MagentoInstallTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoInstallTest extends AbstractTestCommand 16 | { 17 | /** 18 | * @var MagentoInstall 19 | */ 20 | private $command; 21 | 22 | /** 23 | * @var ObjectProphecy|Application 24 | */ 25 | private $application; 26 | 27 | /** 28 | * @var ObjectProphecy|Pull 29 | */ 30 | private $pullCommand; 31 | 32 | 33 | public function setUp() 34 | { 35 | parent::setUp(); 36 | 37 | $this->command = new MagentoInstall($this->commandLine->reveal()); 38 | $this->application = $this->prophesize(Application::class); 39 | $this->pullCommand = $this->prophesize(Pull::class); 40 | 41 | $this->application->getHelperSet()->willReturn(new HelperSet); 42 | $this->application->find('pull')->willReturn($this->pullCommand->reveal()); 43 | 44 | $this->command->setApplication($this->application->reveal()); 45 | } 46 | 47 | public function tearDown() 48 | { 49 | $this->prophet->checkPredictions(); 50 | } 51 | 52 | public function testCommandIsConfigured() 53 | { 54 | static::assertEquals('magento-install', $this->command->getName()); 55 | static::assertEquals(['mi'], $this->command->getAliases()); 56 | static::assertEquals('Runs the magento install script', $this->command->getDescription()); 57 | } 58 | 59 | public function testMagentoInstallCommand() 60 | { 61 | $this->useValidEnvironment(); 62 | 63 | $this->commandLine->run('docker exec -u www-data m2-php magento-install')->shouldBeCalled(); 64 | 65 | $expectedInput = new ArrayInput(['files' => ['app/etc']]); 66 | $this->pullCommand->run($expectedInput, $this->output)->shouldBeCalled(); 67 | $this->output->writeln('Install complete!')->shouldBeCalled(); 68 | 69 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 70 | } 71 | 72 | public function testExceptionThrownIfContainerNameNotFound() 73 | { 74 | $this->useInvalidEnvironment(); 75 | $this->expectException(\RuntimeException::class); 76 | 77 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/Command/MagentoModuleDisableTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoModuleDisableTest extends AbstractTestCommand 16 | { 17 | /** 18 | * @var ComposerUpdate 19 | */ 20 | private $command; 21 | 22 | /** 23 | * @var ObjectProphecy|Application 24 | */ 25 | private $application; 26 | 27 | /** 28 | * @var ObjectProphecy|Pull 29 | */ 30 | private $pullCommand; 31 | 32 | 33 | public function setUp() 34 | { 35 | parent::setUp(); 36 | 37 | $this->command = new MagentoModuleDisable($this->commandLine->reveal()); 38 | $this->application = $this->prophesize(Application::class); 39 | $this->pullCommand = $this->prophesize(Pull::class); 40 | 41 | $this->application->getHelperSet()->willReturn(new HelperSet); 42 | $this->application->find('pull')->willReturn($this->pullCommand->reveal()); 43 | 44 | $this->command->setApplication($this->application->reveal()); 45 | } 46 | 47 | public function tearDown() 48 | { 49 | $this->prophet->checkPredictions(); 50 | } 51 | 52 | public function testCommandIsConfigured() 53 | { 54 | $description = 'Disable Magento module and updates the config.php file'; 55 | 56 | static::assertEquals('module:disable', $this->command->getName()); 57 | static::assertEquals($description, $this->command->getDescription()); 58 | } 59 | 60 | public function testModuleEnableCommand() 61 | { 62 | $this->useValidEnvironment(); 63 | $this->input->getArgument('module')->shouldBeCalled()->willReturn('Jh_Brands'); 64 | 65 | $cmd = 'docker exec -u www-data m2-php bin/magento module:disable Jh_Brands --ansi'; 66 | $this->commandLine->run($cmd); 67 | 68 | $expectedInput = new ArrayInput(['files' => ['app/etc/config.php']]); 69 | $this->pullCommand->run($expectedInput, $this->output)->shouldBeCalled(); 70 | 71 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 72 | } 73 | 74 | public function testExceptionThrownIfContainerNameNotFound() 75 | { 76 | $this->useInvalidEnvironment(); 77 | $this->expectException(\RuntimeException::class); 78 | 79 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/Command/MagentoModuleEnableTest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class MagentoModuleEnableTest extends AbstractTestCommand 16 | { 17 | /** 18 | * @var ComposerUpdate 19 | */ 20 | private $command; 21 | 22 | /** 23 | * @var ObjectProphecy|Application 24 | */ 25 | private $application; 26 | 27 | /** 28 | * @var ObjectProphecy|Pull 29 | */ 30 | private $pullCommand; 31 | 32 | 33 | public function setUp() 34 | { 35 | parent::setUp(); 36 | 37 | $this->command = new MagentoModuleEnable($this->commandLine->reveal()); 38 | $this->application = $this->prophesize(Application::class); 39 | $this->pullCommand = $this->prophesize(Pull::class); 40 | 41 | $this->application->getHelperSet()->willReturn(new HelperSet); 42 | $this->application->find('pull')->willReturn($this->pullCommand->reveal()); 43 | 44 | $this->command->setApplication($this->application->reveal()); 45 | } 46 | 47 | public function tearDown() 48 | { 49 | $this->prophet->checkPredictions(); 50 | } 51 | 52 | public function testCommandIsConfigured() 53 | { 54 | $description = 'Enable Magento module and updates the config.php file'; 55 | 56 | static::assertEquals('module:enable', $this->command->getName()); 57 | static::assertEquals($description, $this->command->getDescription()); 58 | } 59 | 60 | public function testModuleEnableCommand() 61 | { 62 | $this->useValidEnvironment(); 63 | $this->input->getArgument('module')->shouldBeCalled()->willReturn('Jh_Brands'); 64 | 65 | $cmd = 'docker exec -u www-data m2-php bin/magento module:enable Jh_Brands --ansi'; 66 | $this->commandLine->run($cmd)->shouldBeCalled(); 67 | 68 | $expectedInput = new ArrayInput(['files' => ['app/etc/config.php']]); 69 | $this->pullCommand->run($expectedInput, $this->output)->shouldBeCalled(); 70 | 71 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 72 | } 73 | 74 | public function testExceptionThrownIfContainerNameNotFound() 75 | { 76 | $this->useInvalidEnvironment(); 77 | $this->expectException(\RuntimeException::class); 78 | 79 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/Command/MagentoTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class MagentoTest extends AbstractTestCommand 11 | { 12 | /** 13 | * @var Magento 14 | */ 15 | private $command; 16 | 17 | public function setUp() 18 | { 19 | parent::setUp(); 20 | $this->command = new Magento($this->commandLine->reveal()); 21 | } 22 | 23 | public function tearDown() 24 | { 25 | $this->prophet->checkPredictions(); 26 | } 27 | 28 | public function testCommandIsConfigured() 29 | { 30 | $description = 'Works as a proxy to the Magento bin inside the container'; 31 | 32 | static::assertEquals('magento', $this->command->getName()); 33 | static::assertEquals(['mage', 'm'], $this->command->getAliases()); 34 | static::assertEquals($description, $this->command->getDescription()); 35 | static::assertArrayHasKey('cmd', $this->command->getDefinition()->getArguments()); 36 | } 37 | 38 | /** 39 | * @dataProvider magentoCommandProvider 40 | */ 41 | public function testCommandWorksAsAProxy($args) 42 | { 43 | $this->useValidEnvironment(); 44 | 45 | // We have to use $_SERVER['argv'] here 46 | $_SERVER['argv'] = array_merge(['workflow', 'magento'], $args); 47 | 48 | $this->commandLine 49 | ->run(sprintf('docker exec -u www-data m2-php bin/magento --ansi %s', implode(' ', $args))) 50 | ->shouldBeCalled(); 51 | 52 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 53 | } 54 | 55 | public function magentoCommandProvider() : array 56 | { 57 | return [ 58 | [['cache:flush', 'config']], 59 | [['setup:static-content:deploy', '--theme="Luma/default"']], 60 | [['module:status']], 61 | [['module:disable', 'Magento_Weee']] 62 | ]; 63 | } 64 | 65 | public function testCanRunCommandWithoutArgs() 66 | { 67 | $this->useValidEnvironment(); 68 | 69 | // We have to use $_SERVER['argv'] here 70 | $_SERVER['argv'] = ['workflow', 'magento']; 71 | 72 | $this->commandLine->run('docker exec -u www-data m2-php bin/magento --ansi')->shouldBeCalled(); 73 | 74 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 75 | } 76 | 77 | public function testExceptionThrownIfContainerNameNotFound() 78 | { 79 | $this->useInvalidEnvironment(); 80 | $this->expectException(\RuntimeException::class); 81 | 82 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/Command/NginxReloadTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class NginxReloadTest extends AbstractTestCommand 11 | { 12 | /** 13 | * @var NginxReload 14 | */ 15 | private $command; 16 | 17 | public function setUp() 18 | { 19 | parent::setUp(); 20 | $this->command = new NginxReload($this->commandLine->reveal()); 21 | } 22 | 23 | public function tearDown() 24 | { 25 | $this->prophet->checkPredictions(); 26 | } 27 | 28 | public function testCommandIsConfigured() 29 | { 30 | static::assertEquals('nginx-reload', $this->command->getName()); 31 | static::assertEquals(['nginx'], $this->command->getAliases()); 32 | static::assertEquals('Sends reload signal to NGINX in the container', $this->command->getDescription()); 33 | } 34 | 35 | public function testNginxReloadCommand() 36 | { 37 | $this->useValidEnvironment(); 38 | 39 | $this->commandLine->run('docker exec m2 nginx -s "reload"')->shouldBeCalled(); 40 | $this->output->writeln('Reload signal sent')->shouldBeCalled(); 41 | 42 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 43 | } 44 | 45 | public function testExceptionThrownIfContainerNameNotFound() 46 | { 47 | $this->useInvalidEnvironment(); 48 | $this->expectException(\RuntimeException::class); 49 | 50 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/Command/PhpTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class PhpTest extends AbstractTestCommand 11 | { 12 | /** 13 | * @var Ssh 14 | */ 15 | private $command; 16 | 17 | public function setUp() 18 | { 19 | parent::setUp(); 20 | $this->command = new Php($this->commandLine->reveal()); 21 | } 22 | 23 | public function tearDown() 24 | { 25 | $this->prophet->checkPredictions(); 26 | } 27 | 28 | public function testCommandIsConfigured() 29 | { 30 | static::assertEquals('php', $this->command->getName()); 31 | static::assertEmpty($this->command->getAliases()); 32 | static::assertEquals('Run a php script on the app container', $this->command->getDescription()); 33 | static::assertArrayHasKey('php-file', $this->command->getDefinition()->getArguments()); 34 | } 35 | 36 | public function testPhpCommand() 37 | { 38 | $this->useValidEnvironment(); 39 | $this->input->getArgument('php-file')->willReturn('my-file.php'); 40 | 41 | $this->commandLine->runInteractively('docker exec -it -u www-data m2-php php my-file.php')->shouldBeCalled(); 42 | 43 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 44 | } 45 | 46 | public function testExceptionThrownIfComposeFileMissingImageTag() 47 | { 48 | $this->useInvalidEnvironment(); 49 | $this->expectException(\RuntimeException::class); 50 | 51 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Command/RestartTest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class RestartTest extends AbstractTestCommand 17 | { 18 | /** 19 | * @var Restart 20 | */ 21 | private $command; 22 | 23 | /** 24 | * @var ObjectProphecy|Application 25 | */ 26 | private $application; 27 | 28 | /** 29 | * @var ObjectProphecy|Up 30 | */ 31 | private $upCommand; 32 | 33 | /** 34 | * @var ObjectProphecy|Stop 35 | */ 36 | private $stopCommand; 37 | 38 | 39 | public function setUp() 40 | { 41 | parent::setUp(); 42 | 43 | $this->command = new Restart(); 44 | $this->application = $this->prophesize(Application::class); 45 | $this->upCommand = $this->prophesize(Up::class); 46 | $this->stopCommand = $this->prophesize(Stop::class); 47 | 48 | $this->application->getHelperSet()->willReturn(new HelperSet); 49 | $this->application->find('up')->willReturn($this->upCommand->reveal()); 50 | $this->application->find('stop')->willReturn($this->stopCommand->reveal()); 51 | 52 | $this->command->setApplication($this->application->reveal()); 53 | } 54 | 55 | public function tearDown() 56 | { 57 | $this->prophet->checkPredictions(); 58 | } 59 | 60 | public function testCommandIsConfigured() 61 | { 62 | static::assertEquals('restart', $this->command->getName()); 63 | static::assertEquals([], $this->command->getAliases()); 64 | static::assertEquals('Restarts the containers', $this->command->getDescription()); 65 | static::assertArrayHasKey('prod', $this->command->getDefinition()->getOptions()); 66 | } 67 | 68 | public function testCommandRunsAllSubCommands() 69 | { 70 | $this->application->find('stop')->shouldBeCalled(); 71 | $this->application->find('up')->shouldBeCalled(); 72 | 73 | $this->stopCommand->run($this->input, $this->output)->shouldBeCalled(); 74 | $this->upCommand->run($this->input, $this->output)->shouldBeCalled(); 75 | 76 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Command/StopTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class StopTest extends AbstractTestCommand 11 | { 12 | /** 13 | * @var Stop 14 | */ 15 | private $command; 16 | 17 | public function setUp() 18 | { 19 | parent::setUp(); 20 | $this->command = new Stop($this->commandLine->reveal()); 21 | } 22 | 23 | public function tearDown() 24 | { 25 | $this->prophet->checkPredictions(); 26 | } 27 | 28 | public function testCommandIsConfigured() 29 | { 30 | static::assertEquals('stop', $this->command->getName()); 31 | static::assertEquals([], $this->command->getAliases()); 32 | static::assertEquals('Stops the containers running', $this->command->getDescription()); 33 | static::assertArrayHasKey('prod', $this->command->getDefinition()->getOptions()); 34 | } 35 | 36 | public function testStopsDevelopmentMode() 37 | { 38 | $this->useValidEnvironment(); 39 | 40 | $this->commandLine 41 | ->run('docker-compose -f docker-compose.yml -f docker-compose.dev.yml stop') 42 | ->shouldBeCalled(); 43 | 44 | $this->output->writeln('Containers stopped')->shouldBeCalled(); 45 | 46 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 47 | } 48 | 49 | public function testStopsProductionMode() 50 | { 51 | $this->useValidEnvironment(); 52 | 53 | $this->input->getOption('prod')->willReturn(true); 54 | 55 | $this->commandLine 56 | ->run('docker-compose -f docker-compose.yml -f docker-compose.prod.yml stop') 57 | ->shouldBeCalled(); 58 | 59 | $this->output->writeln('Containers stopped')->shouldBeCalled(); 60 | 61 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/Command/SyncTest.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class SyncTest extends AbstractTestCommand 19 | { 20 | /** 21 | * @var Sync 22 | */ 23 | private $command; 24 | 25 | /** 26 | * @var ObjectProphecy|Push 27 | */ 28 | private $pushCommand; 29 | 30 | /** 31 | * @var ObjectProphecy|Application 32 | */ 33 | private $application; 34 | 35 | public function setUp() 36 | { 37 | parent::setUp(); 38 | $this->command = new Sync($this->commandLine->reveal()); 39 | $this->application = $this->prophesize(Application::class); 40 | $this->pushCommand = $this->prophesize(Push::class); 41 | 42 | $this->application->getHelperSet()->willReturn(new HelperSet); 43 | $this->application->find('push')->willReturn($this->pushCommand->reveal()); 44 | 45 | $this->command->setApplication($this->application->reveal()); 46 | } 47 | 48 | public function tearDown() 49 | { 50 | $this->prophet->checkPredictions(); 51 | } 52 | 53 | public function testCommandIsConfigured() 54 | { 55 | $description = 'Syncs changes from the host filesystem to the relevant docker containers'; 56 | 57 | static::assertEquals('sync', $this->command->getName()); 58 | static::assertEquals([], $this->command->getAliases()); 59 | static::assertEquals($description, $this->command->getDescription()); 60 | } 61 | 62 | public function testSyncWillAddAFileWhenItExists() 63 | { 64 | $this->useValidEnvironment(); 65 | 66 | $this->input->getArgument('file')->willReturn('some-file.txt'); 67 | 68 | $expectedInput = new ArrayInput(['files' => ['some-file.txt']]); 69 | $this->pushCommand->run($expectedInput, $this->output)->shouldBeCalled(); 70 | 71 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 72 | } 73 | 74 | public function testSyncWillDeleteAFileWhenItDoesntExist() 75 | { 76 | $this->useValidEnvironment(); 77 | 78 | $this->input->getArgument('file')->willReturn('some-deleted-file.txt'); 79 | 80 | $this->commandLine->run('docker exec m2-php rm -rf /var/www/some-deleted-file.txt')->shouldBeCalled(); 81 | $this->output->writeln(' x some-deleted-file.txt > m2-php ')->shouldBeCalled(); 82 | 83 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 84 | } 85 | 86 | public function testExceptionThrownIfComposeFileMissingImageTag() 87 | { 88 | $this->useInvalidEnvironment(); 89 | $this->expectException(\RuntimeException::class); 90 | 91 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/Command/VarnishDisableTest.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class VarnishDisableTest extends AbstractTestCommand 12 | { 13 | /** 14 | * @var NginxReload 15 | */ 16 | private $command; 17 | 18 | public function setUp() 19 | { 20 | parent::setUp(); 21 | $this->command = new VarnishDisable($this->commandLine->reveal()); 22 | } 23 | 24 | public function tearDown() 25 | { 26 | $this->prophet->checkPredictions(); 27 | } 28 | 29 | public function testCommandIsConfigured() 30 | { 31 | static::assertEquals('varnish-disable', $this->command->getName()); 32 | static::assertEquals(['vd'], $this->command->getAliases()); 33 | static::assertEquals('Switches the VCL to be a proxy', $this->command->getDescription()); 34 | } 35 | 36 | public function testVarnishEnableCommand() 37 | { 38 | $this->useValidEnvironment(); 39 | 40 | $this->commandLine->run('docker-compose exec -T varnish varnishadm vcl.use boot')->shouldBeCalled(); 41 | $this->output->writeln('Varnish caching disabled')->shouldBeCalled(); 42 | 43 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/Command/VarnishEnableTest.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class VarnishEnableTest extends AbstractTestCommand 12 | { 13 | /** 14 | * @var NginxReload 15 | */ 16 | private $command; 17 | 18 | public function setUp() 19 | { 20 | parent::setUp(); 21 | $this->command = new VarnishEnable($this->commandLine->reveal()); 22 | } 23 | 24 | public function tearDown() 25 | { 26 | $this->prophet->checkPredictions(); 27 | } 28 | 29 | public function testCommandIsConfigured() 30 | { 31 | static::assertEquals('varnish-enable', $this->command->getName()); 32 | static::assertEquals(['ve'], $this->command->getAliases()); 33 | static::assertEquals('Switches the VCL to use caching', $this->command->getDescription()); 34 | } 35 | 36 | public function testVarnishEnableCommand() 37 | { 38 | $this->useValidEnvironment(); 39 | 40 | $this->commandLine->run('docker-compose exec -T varnish varnishadm vcl.use boot0')->shouldBeCalled(); 41 | $this->output->writeln('Varnish caching enabled')->shouldBeCalled(); 42 | 43 | $this->command->execute($this->input->reveal(), $this->output->reveal()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/CommandLineTest.php: -------------------------------------------------------------------------------- 1 | loop = new StreamSelectLoop; 33 | $this->output = new BufferedOutput; 34 | $this->commandLine = new CommandLine($this->loop, new NullLogger, $this->output); 35 | } 36 | 37 | /** 38 | * @expectedException \Jh\Workflow\ProcessFailedException 39 | */ 40 | public function testRunThrowsExceptionIfCommandFails() 41 | { 42 | $this->commandLine->run('exit 1'); 43 | } 44 | 45 | public function testRunOutputsAndReturnsCommandOutput() 46 | { 47 | $out = $this->commandLine->run('echo "yes"'); 48 | 49 | self::assertEquals("yes\n", $out); 50 | self::assertEquals("yes\n", $this->output->fetch()); 51 | } 52 | 53 | public function testRunQuietlyDoesNotOutputButReturnsOutput() 54 | { 55 | $out = $this->commandLine->runQuietly('echo "yes"'); 56 | 57 | self::assertEquals("yes\n", $out); 58 | self::assertEquals('', $this->output->fetch()); 59 | } 60 | 61 | public function testRunAsyncOutputsAndExecutesCallBackAfterCommand() 62 | { 63 | $ran = false; 64 | $this->commandLine->runAsync('echo "yes"', function () use (&$ran) { 65 | $ran = true; 66 | }); 67 | 68 | $this->loop->run(); 69 | 70 | self::assertEquals("yes\n", $this->output->fetch()); 71 | self::assertTrue($ran); 72 | } 73 | 74 | public function testRunLogsCommand() 75 | { 76 | $logger = $this->prophesize(Logger::class); 77 | $this->commandLine = new CommandLine($this->loop, $logger->reveal(), $this->output); 78 | 79 | $this->commandLine->run('echo "yes"'); 80 | 81 | $logger->logCommand('echo "yes"', 'normal')->shouldHaveBeenCalled(); 82 | } 83 | 84 | public function testRunQuietlyLogsCommand() 85 | { 86 | $logger = $this->prophesize(Logger::class); 87 | $this->commandLine = new CommandLine($this->loop, $logger->reveal(), $this->output); 88 | 89 | $this->commandLine->runQuietly('echo "yes"'); 90 | 91 | $logger->logCommand('echo "yes"', 'quiet')->shouldHaveBeenCalled(); 92 | } 93 | 94 | public function testRunAsyncLogsCommand() 95 | { 96 | $logger = $this->prophesize(Logger::class); 97 | $this->commandLine = new CommandLine($this->loop, $logger->reveal(), $this->output); 98 | 99 | $this->commandLine->runAsync('echo "yes"'); 100 | 101 | $logger->logCommand('echo "yes"', 'async')->shouldHaveBeenCalled(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test/LoggerTest.php: -------------------------------------------------------------------------------- 1 | log = sprintf('%s/%s/workflow.log', sys_get_temp_dir(), $this->getName()); 16 | } 17 | public function testLogger() 18 | { 19 | $logger = new Logger(new BufferedOutput); 20 | $logger->setLogFile($this->log); 21 | 22 | $logger->debug('DEBUG MESSAGE'); 23 | 24 | $expected = "/\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \(DEBUG\) DEBUG MESSAGE\\n/"; 25 | 26 | self::assertRegExp($expected, file_get_contents($this->log)); 27 | } 28 | 29 | public function testLogCommandLogsAndPrints() 30 | { 31 | $logger = new Logger($output = new BufferedOutput); 32 | $logger->setLogFile($this->log); 33 | 34 | $logger->logCommand('echo "yes"', 'async'); 35 | 36 | $expected = "/\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \(DEBUG\) Executing command \[async\]: \"echo \"yes\"\"\n/"; 37 | 38 | self::assertRegExp($expected, file_get_contents($this->log)); 39 | self::assertEquals("Executing [async] echo \"yes\"\n", $output->fetch()); 40 | } 41 | 42 | public function tearDown() 43 | { 44 | unlink($this->log); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/Test/Constraint/FileExistsInContainerTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @runTestsInSeparateProcesses Because you cannot mock global functions which already used 12 | */ 13 | class FileExistsInContainerTest extends TestCase 14 | { 15 | use PHPMock; 16 | 17 | public function testMatchesWhenFileExists() 18 | { 19 | $exec = $this->getFunctionMock('Jh\Workflow\Test\Constraint', 'exec'); 20 | $exec->expects($this->once())->willReturnCallback( 21 | function ($command, &$output, &$exitCode) { 22 | self::assertEquals('docker exec m2-php test -e some-file.txt', $command); 23 | $exitCode = 0; 24 | } 25 | ); 26 | 27 | self::assertTrue((new FileExistsInContainer('m2-php'))->matches('some-file.txt')); 28 | } 29 | 30 | public function testMatchesWhenFileDoesNotExist() 31 | { 32 | $exec = $this->getFunctionMock('Jh\Workflow\Test\Constraint', 'exec'); 33 | $exec->expects($this->once())->willReturnCallback( 34 | function ($command, &$output, &$exitCode) { 35 | self::assertEquals('docker exec m2-php test -e some-file.txt', $command); 36 | $exitCode = 1; 37 | } 38 | ); 39 | 40 | self::assertFalse((new FileExistsInContainer('m2-php'))->matches('some-file.txt')); 41 | } 42 | 43 | public function testToString() 44 | { 45 | self::assertEquals('exists in container m2-php', (new FileExistsInContainer('m2-php'))->toString()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/Test/Constraint/FileGroupAndOwnerInContainerTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @runTestsInSeparateProcesses Because you cannot mock global functions which already used 12 | */ 13 | class FileGroupAndOwnerInContainerTest extends TestCase 14 | { 15 | use PHPMock; 16 | 17 | public function testMatchesWhenFileUserAndGroupCorrect() 18 | { 19 | $exec = $this->getFunctionMock('Jh\Workflow\Test\Constraint', 'exec'); 20 | $exec->expects($this->once())->willReturnCallback( 21 | function ($command, &$output, &$exitCode) { 22 | self::assertEquals('docker exec m2-php stat -c "%G:%U" some-file.txt', $command); 23 | $exitCode = 0; 24 | $output = ['www-data:www-data']; 25 | } 26 | ); 27 | 28 | self::assertTrue( 29 | (new FileUserAndGroupInContainer('m2-php', 'www-data', 'www-data'))->matches('some-file.txt') 30 | ); 31 | } 32 | 33 | public function testMatchesWhenFileUserAndGroupIncorrect() 34 | { 35 | $exec = $this->getFunctionMock('Jh\Workflow\Test\Constraint', 'exec'); 36 | $exec->expects($this->once())->willReturnCallback( 37 | function ($command, &$output, &$exitCode) { 38 | self::assertEquals('docker exec m2-php stat -c "%G:%U" some-file.txt', $command); 39 | $exitCode = 0; 40 | $output = ['root:root']; 41 | } 42 | ); 43 | 44 | self::assertFalse( 45 | (new FileUserAndGroupInContainer('m2-php', 'www-data', 'www-data'))->matches('some-file.txt') 46 | ); 47 | } 48 | 49 | public function testMatchesWhenFileDoesNotExist() 50 | { 51 | $exec = $this->getFunctionMock('Jh\Workflow\Test\Constraint', 'exec'); 52 | $exec->expects($this->once())->willReturnCallback( 53 | function ($command, &$output, &$exitCode) { 54 | self::assertEquals('docker exec m2-php stat -c "%G:%U" some-file.txt', $command); 55 | $exitCode = 1; 56 | } 57 | ); 58 | 59 | self::assertFalse( 60 | (new FileUserAndGroupInContainer('m2-php', 'www-data', 'www-data'))->matches('some-file.txt') 61 | ); 62 | } 63 | 64 | public function testToString() 65 | { 66 | self::assertEquals( 67 | 'has correct group and user in container m2-php', 68 | (new FileUserAndGroupInContainer('m2-php', 'www-data', 'www-data'))->toString() 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/WatchFactoryTest.php: -------------------------------------------------------------------------------- 1 | create(['app']); 15 | 16 | self::assertInstanceOf(FsWatch::class, $watcher); 17 | } 18 | 19 | public function testWatchFactoryWithExcludes() 20 | { 21 | $watcher = (new WatchFactory(new StreamSelectLoop))->create(['app'], ['exclude-me']); 22 | 23 | self::assertInstanceOf(FsWatch::class, $watcher); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/broken-env/.docker/local.env: -------------------------------------------------------------------------------- 1 | # Copy to local.env and store on local machine 2 | 3 | # Host Config 4 | MAGE_ROOT_DIR=/var/www 5 | MAGE_HOST=https://m2.dev 6 | MAGE_ADMIN_USER=admin 7 | MAGE_ADMIN_PASS=password123 8 | MAGE_ADMIN_FIRSTNAME=Joe 9 | MAGE_ADMIN_LASTNAME=Bloggs 10 | MAGE_ADMIN_EMAIL=magento@wearejh.com 11 | MAGE_BACKEND_FRONTNAME=admin 12 | HTTPS=on 13 | 14 | # MySQL Details 15 | MYSQL_ROOT_PASSWORD=docker 16 | MYSQL_DATABASE=docker 17 | MYSQL_USER=docker 18 | MYSQL_PASSWORD=docker 19 | 20 | # PHP 21 | PHP_MEMORY_LIMIT=2G 22 | 23 | # RabbitMQ 24 | RABBITMQ_DEFAULT_USER=user 25 | RABBITMQ_DEFAULT_PASS=password 26 | 27 | ## Mail config 28 | MAIL_HOST=mail 29 | MAIL_PORT=1025 30 | 31 | ## Xdebug config 32 | XDEBUG_IDE_KEY=PHPSTORM 33 | XDEBUG_CONFIG=remote_host=10.254.254.254 34 | XDEBUG_ENABLE=true 35 | 36 | # Blackfire 37 | BLACKFIRE_CLIENT_ID= 38 | BLACKFIRE_CLIENT_TOKEN= 39 | BLACKFIRE_SERVER_ID= 40 | BLACKFIRE_SERVER_TOKEN= -------------------------------------------------------------------------------- /test/fixtures/broken-env/app.php.dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Michael Woodward 3 | 4 | ARG BUILD_ENV=dev 5 | ENV PROD_ENV=prod 6 | 7 | CMD ["php-fpm"] -------------------------------------------------------------------------------- /test/fixtures/broken-env/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx: 5 | volumes: 6 | - .docker/certs:/etc/letsencrypt 7 | env_file: 8 | - ./.docker/local.env 9 | 10 | php: 11 | env_file: 12 | - .docker/local.env 13 | volumes: 14 | - .docker/composer-cache:/var/www/.docker/composer-cache 15 | 16 | db: 17 | env_file: 18 | - ./.docker/local.env 19 | volumes: 20 | - .docker/db/:/docker-entrypoint-initdb.d/ 21 | 22 | # rabbitmq: 23 | # env_file: 24 | # - ./.docker/local.env 25 | 26 | mail: 27 | container_name: m2-mail 28 | image: schickling/mailcatcher 29 | ports: 30 | - 1025 31 | - 1080:1080 32 | 33 | blackfire: 34 | container_name: m2-blackfire 35 | image: blackfire/blackfire 36 | env_file: 37 | - .docker/local.env 38 | -------------------------------------------------------------------------------- /test/fixtures/broken-env/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | db-data: 5 | app-pub: 6 | app-var: 7 | app-env: 8 | 9 | services: 10 | nginx: 11 | container_name: m2 12 | image: nginx:stable-alpine 13 | volumes: 14 | - app-pub:/var/www/pub 15 | - .docker/nginx/sites:/etc/nginx/conf.d 16 | working_dir: /var/www 17 | ports: 18 | - "80:80" 19 | - "443:443" 20 | 21 | php: 22 | container_name: m2-php 23 | image: wearejh/m2 24 | build: 25 | context: ./ 26 | dockerfile: app.php.dockerfile 27 | volumes: 28 | - app-pub:/var/www/pub 29 | - app-env:/var/www/app/etc 30 | - ~/.composer/auth.json:/root/.composer/auth.json 31 | working_dir: /var/www 32 | ports: 33 | - 9000 34 | 35 | db: 36 | container_name: m2-db 37 | image: mysql:5.6 38 | volumes: 39 | - db-data:/var/lib/mysql 40 | ports: 41 | - "3306:3306" 42 | restart: unless-stopped 43 | 44 | redis: 45 | container_name: m2-redis 46 | image: redis:3-alpine 47 | ports: 48 | - "6379:6379" 49 | 50 | # elasticsearch: 51 | # image: elasticsearch 52 | # ports: 53 | # - "9200:9200" 54 | # - "9300:9300" 55 | 56 | # rabbitmq: 57 | # image: rabbitmq:3.6.1-management 58 | # ports: 59 | # - "15672:15672" 60 | # - "5672:5672" 61 | -------------------------------------------------------------------------------- /test/fixtures/config/env.default.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'frontName' => 'admin', 6 | ], 7 | 'db' => [ 8 | 'connection' => [ 9 | 'indexer' => [ 10 | 'host' => 'db', 11 | 'dbname' => 'docker', 12 | 'username' => 'docker', 13 | 'password' => 'docker', 14 | 'model' => 'mysql4', 15 | 'engine' => 'innodb', 16 | 'initStatements' => 'SET NAMES utf8;', 17 | 'active' => '1', 18 | 'persistent' => NULL, 19 | ], 20 | 'default' => [ 21 | 'host' => 'db', 22 | 'dbname' => 'docker', 23 | 'username' => 'docker', 24 | 'password' => 'docker', 25 | 'model' => 'mysql4', 26 | 'engine' => 'innodb', 27 | 'initStatements' => 'SET NAMES utf8;', 28 | 'active' => '1', 29 | ], 30 | ], 31 | 'table_prefix' => '', 32 | ], 33 | 'resource' => [ 34 | 'default_setup' => [ 35 | 'connection' => 'default', 36 | ], 37 | ], 38 | 'x-frame-options' => 'SAMEORIGIN', 39 | 'MAGE_MODE' => 'developer', 40 | 'session' => [ 41 | 'save' => 'redis', 42 | 'redis' => [ 43 | 'host' => 'redis', 44 | 'port' => '6379', 45 | 'password' => '', 46 | 'timeout' => '2.5', 47 | 'persistent_identifier' => '', 48 | 'database' => '0', 49 | 'compression_threshold' => '2048', 50 | 'compression_library' => 'gzip', 51 | 'log_level' => '1', 52 | 'max_concurrency' => '6', 53 | 'break_after_frontend' => '5', 54 | 'break_after_adminhtml' => '30', 55 | 'first_lifetime' => '600', 56 | 'bot_first_lifetime' => '60', 57 | 'bot_lifetime' => '7200', 58 | 'disable_locking' => '0', 59 | 'min_lifetime' => '60', 60 | 'max_lifetime' => '2592000', 61 | ], 62 | ], 63 | 'cache_types' => [ 64 | 'config' => 1, 65 | 'layout' => 1, 66 | 'block_html' => 1, 67 | 'collections' => 1, 68 | 'reflection' => 1, 69 | 'db_ddl' => 1, 70 | 'eav' => 1, 71 | 'customer_notification' => 1, 72 | 'config_integration' => 1, 73 | 'config_integration_api' => 1, 74 | 'target_rule' => 1, 75 | 'full_page' => 1, 76 | 'amasty_shopby' => 1, 77 | 'translate' => 1, 78 | 'config_webservice' => 1, 79 | ], 80 | 'install' => [ 81 | 'date' => 'Mon, 05 Mar 2018 11:35:35 +0000', 82 | ], 83 | 'cache' => [ 84 | 'frontend' => [ 85 | 'default' => [ 86 | 'backend' => 'Cm_Cache_Backend_Redis', 87 | 'backend_options' => [ 88 | 'server' => 'redis', 89 | 'port' => '6379', 90 | 'database' => '1', 91 | ], 92 | ], 93 | ], 94 | ], 95 | # 'queue' => [ 96 | # 'amqp' => [ 97 | # 'host' => 'rabbitmq', 98 | # 'port' => '5672', 99 | # 'user' => getenv('RABBITMQ_DEFAULT_USER'), 100 | # 'password' => getenv('RABBITMQ_DEFAULT_PASS'), 101 | # 'virtualhost' => '/', 102 | # 'ssl' => '', 103 | # ], 104 | # ], 105 | ]; 106 | -------------------------------------------------------------------------------- /test/fixtures/config/env.production.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'frontName' => 'admin', 6 | ], 7 | 'db' => [ 8 | 'connection' => [ 9 | 'indexer' => [ 10 | 'host' => 'db', 11 | 'dbname' => 'docker', 12 | 'username' => 'docker', 13 | 'password' => 'docker', 14 | 'model' => 'mysql4', 15 | 'engine' => 'innodb', 16 | 'initStatements' => 'SET NAMES utf8;', 17 | 'active' => '1', 18 | 'persistent' => NULL, 19 | ], 20 | 'default' => [ 21 | 'host' => 'db', 22 | 'dbname' => 'docker', 23 | 'username' => 'docker', 24 | 'password' => 'docker', 25 | 'model' => 'mysql4', 26 | 'engine' => 'innodb', 27 | 'initStatements' => 'SET NAMES utf8;', 28 | 'active' => '1', 29 | ], 30 | ], 31 | 'table_prefix' => '', 32 | ], 33 | 'resource' => [ 34 | 'default_setup' => [ 35 | 'connection' => 'default', 36 | ], 37 | ], 38 | 'x-frame-options' => 'SAMEORIGIN', 39 | 'MAGE_MODE' => 'production', 40 | 'session' => [ 41 | 'save' => 'redis', 42 | 'redis' => [ 43 | 'host' => 'redis', 44 | 'port' => '6379', 45 | 'password' => '', 46 | 'timeout' => '2.5', 47 | 'persistent_identifier' => '', 48 | 'database' => '0', 49 | 'compression_threshold' => '2048', 50 | 'compression_library' => 'gzip', 51 | 'log_level' => '1', 52 | 'max_concurrency' => '6', 53 | 'break_after_frontend' => '5', 54 | 'break_after_adminhtml' => '30', 55 | 'first_lifetime' => '600', 56 | 'bot_first_lifetime' => '60', 57 | 'bot_lifetime' => '7200', 58 | 'disable_locking' => '0', 59 | 'min_lifetime' => '60', 60 | 'max_lifetime' => '2592000', 61 | ], 62 | ], 63 | 'cache_types' => [ 64 | 'config' => 1, 65 | 'layout' => 1, 66 | 'block_html' => 1, 67 | 'collections' => 1, 68 | 'reflection' => 1, 69 | 'db_ddl' => 1, 70 | 'eav' => 1, 71 | 'customer_notification' => 1, 72 | 'config_integration' => 1, 73 | 'config_integration_api' => 1, 74 | 'target_rule' => 1, 75 | 'full_page' => 1, 76 | 'amasty_shopby' => 1, 77 | 'translate' => 1, 78 | 'config_webservice' => 1, 79 | ], 80 | 'install' => [ 81 | 'date' => 'Mon, 05 Mar 2018 11:35:35 +0000', 82 | ], 83 | 'cache' => [ 84 | 'frontend' => [ 85 | 'default' => [ 86 | 'backend' => 'Cm_Cache_Backend_Redis', 87 | 'backend_options' => [ 88 | 'server' => 'redis', 89 | 'port' => '6379', 90 | 'database' => '1', 91 | ], 92 | ], 93 | ], 94 | ], 95 | # 'queue' => [ 96 | # 'amqp' => [ 97 | # 'host' => 'rabbitmq', 98 | # 'port' => '5672', 99 | # 'user' => getenv('RABBITMQ_DEFAULT_USER'), 100 | # 'password' => getenv('RABBITMQ_DEFAULT_PASS'), 101 | # 'virtualhost' => '/', 102 | # 'ssl' => '', 103 | # ], 104 | # ], 105 | ]; 106 | -------------------------------------------------------------------------------- /test/fixtures/config/env.queue.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'frontName' => 'admin', 6 | ], 7 | 'db' => [ 8 | 'connection' => [ 9 | 'indexer' => [ 10 | 'host' => 'db', 11 | 'dbname' => 'docker', 12 | 'username' => 'docker', 13 | 'password' => 'docker', 14 | 'model' => 'mysql4', 15 | 'engine' => 'innodb', 16 | 'initStatements' => 'SET NAMES utf8;', 17 | 'active' => '1', 18 | 'persistent' => NULL, 19 | ], 20 | 'default' => [ 21 | 'host' => 'db', 22 | 'dbname' => 'docker', 23 | 'username' => 'docker', 24 | 'password' => 'docker', 25 | 'model' => 'mysql4', 26 | 'engine' => 'innodb', 27 | 'initStatements' => 'SET NAMES utf8;', 28 | 'active' => '1', 29 | ], 30 | ], 31 | 'table_prefix' => '', 32 | ], 33 | 'resource' => [ 34 | 'default_setup' => [ 35 | 'connection' => 'default', 36 | ], 37 | ], 38 | 'x-frame-options' => 'SAMEORIGIN', 39 | 'MAGE_MODE' => 'developer', 40 | 'session' => [ 41 | 'save' => 'redis', 42 | 'redis' => [ 43 | 'host' => 'redis', 44 | 'port' => '6379', 45 | 'password' => '', 46 | 'timeout' => '2.5', 47 | 'persistent_identifier' => '', 48 | 'database' => '0', 49 | 'compression_threshold' => '2048', 50 | 'compression_library' => 'gzip', 51 | 'log_level' => '1', 52 | 'max_concurrency' => '6', 53 | 'break_after_frontend' => '5', 54 | 'break_after_adminhtml' => '30', 55 | 'first_lifetime' => '600', 56 | 'bot_first_lifetime' => '60', 57 | 'bot_lifetime' => '7200', 58 | 'disable_locking' => '0', 59 | 'min_lifetime' => '60', 60 | 'max_lifetime' => '2592000', 61 | ], 62 | ], 63 | 'cache_types' => [ 64 | 'config' => 1, 65 | 'layout' => 1, 66 | 'block_html' => 1, 67 | 'collections' => 1, 68 | 'reflection' => 1, 69 | 'db_ddl' => 1, 70 | 'eav' => 1, 71 | 'customer_notification' => 1, 72 | 'config_integration' => 1, 73 | 'config_integration_api' => 1, 74 | 'target_rule' => 1, 75 | 'full_page' => 1, 76 | 'amasty_shopby' => 1, 77 | 'translate' => 1, 78 | 'config_webservice' => 1, 79 | ], 80 | 'install' => [ 81 | 'date' => 'Mon, 05 Mar 2018 11:35:35 +0000', 82 | ], 83 | 'cache' => [ 84 | 'frontend' => [ 85 | 'default' => [ 86 | 'backend' => 'Cm_Cache_Backend_Redis', 87 | 'backend_options' => [ 88 | 'server' => 'redis', 89 | 'port' => '6379', 90 | 'database' => '1', 91 | ], 92 | ], 93 | ], 94 | ], 95 | 'queue' => [ 96 | 'amqp' => [ 97 | 'host' => 'rabbitmq', 98 | 'port' => '5672', 99 | 'user' => getenv('RABBITMQ_DEFAULT_USER'), 100 | 'password' => getenv('RABBITMQ_DEFAULT_PASS'), 101 | 'virtualhost' => '/', 102 | 'ssl' => '', 103 | ], 104 | ], 105 | ]; 106 | -------------------------------------------------------------------------------- /test/fixtures/config/local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | 26 | 27 | 28 | 29 | Mage_Cache_Backend_Redis 30 | 31 | redis 32 | 6379 33 | 34 | 0 35 | 36 | 0 37 | 1 38 | 10 39 | 0 40 | 1 41 | 1 42 | 20480 43 | gzip 44 | 45 | 46 | 47 | db 48 | 49 | redis 50 | 6379 51 | 2.5 52 | 53 | 1 54 | 2048 55 | gzip 56 | 0 57 | 15 58 | 5 59 | 30 60 | 7200 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /test/fixtures/invalid-env/.docker/local.env.dist: -------------------------------------------------------------------------------- 1 | # Copy to local.env and store on local machine 2 | 3 | sds dfsahdf ukasdf asdf 4 | 5 | # Host Config 6 | MAGE_ROOT_DIR=/var/www 7 | MAGE_HOST=https://m2.dev 8 | MAGE_ADMIN_USER=admin 9 | MAGE_ADMIN_PASS=password123 10 | MAGE_ADMIN_FIRSTNAME=Joe 11 | MAGE_ADMIN_LASTNAME=Bloggs 12 | MAGE_ADMIN_EMAIL=magento@wearejh.com 13 | MAGE_BACKEND_FRONTNAME=admin 14 | HTTPS=on 15 | 16 | # MySQL Details 17 | MYSQL_ROOT_PASSWORD=docker 18 | MYSQL_DATABASE=docker 19 | MYSQL_USER=docker 20 | MYSQL_PASSWORD=docker 21 | 22 | # PHP 23 | PHP_MEMORY_LIMIT=2G 24 | 25 | # RabbitMQ 26 | RABBITMQ_DEFAULT_USER=user 27 | RABBITMQ_DEFAULT_PASS=password 28 | 29 | ## Mail config 30 | MAIL_HOST=mail 31 | MAIL_PORT=1025 32 | 33 | ## Xdebug config 34 | XDEBUG_IDE_KEY=PHPSTORM 35 | XDEBUG_CONFIG=remote_host=10.254.254.254 36 | XDEBUG_ENABLE=true 37 | 38 | # Blackfire 39 | BLACKFIRE_CLIENT_ID= 40 | BLACKFIRE_CLIENT_TOKEN= 41 | BLACKFIRE_SERVER_ID= 42 | BLACKFIRE_SERVER_TOKEN= -------------------------------------------------------------------------------- /test/fixtures/invalid-env/app.php.dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Michael Woodward 3 | 4 | ARG BUILD_ENV=dev 5 | ENV PROD_ENV=prod 6 | 7 | CMD ["php-fpm"] -------------------------------------------------------------------------------- /test/fixtures/invalid-env/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx: 5 | volumes: 6 | - .docker/certs:/etc/letsencrypt 7 | env_file: 8 | - ./.docker/local.env 9 | 10 | php: 11 | env_file: 12 | - .docker/local.env 13 | volumes: 14 | - .docker/composer-cache:/var/www/.docker/composer-cache 15 | 16 | db: 17 | env_file: 18 | - ./.docker/local.env 19 | volumes: 20 | - .docker/db/:/docker-entrypoint-initdb.d/ 21 | 22 | # rabbitmq: 23 | # env_file: 24 | # - ./.docker/local.env 25 | 26 | mail: 27 | container_name: m2-mail 28 | image: schickling/mailcatcher 29 | ports: 30 | - 1025 31 | - 1080:1080 32 | 33 | blackfire: 34 | container_name: m2-blackfire 35 | image: blackfire/blackfire 36 | env_file: 37 | - .docker/local.env 38 | -------------------------------------------------------------------------------- /test/fixtures/invalid-env/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | db-data: 5 | app-pub: 6 | app-var: 7 | app-env: 8 | 9 | services: 10 | nginx: 11 | image: nginx:stable-alpine 12 | volumes: 13 | - app-pub:/var/www/pub 14 | - .docker/nginx/sites:/etc/nginx/conf.d 15 | working_dir: /var/www 16 | ports: 17 | - "80:80" 18 | - "443:443" 19 | 20 | php: 21 | build: 22 | context: ./ 23 | dockerfile: app.php.dockerfile 24 | volumes: 25 | - app-pub:/var/www/pub 26 | - app-env:/var/www/app/etc 27 | - ~/.composer/auth.json:/root/.composer/auth.json 28 | working_dir: /var/www 29 | ports: 30 | - 9000 31 | 32 | db: 33 | container_name: m2-db 34 | image: mysql:5.6 35 | volumes: 36 | - db-data:/var/lib/mysql 37 | ports: 38 | - "3306:3306" 39 | restart: unless-stopped 40 | 41 | redis: 42 | container_name: m2-redis 43 | image: redis:3-alpine 44 | ports: 45 | - "6379:6379" 46 | 47 | # elasticsearch: 48 | # image: elasticsearch 49 | # ports: 50 | # - "9200:9200" 51 | # - "9300:9300" 52 | 53 | # rabbitmq: 54 | # image: rabbitmq:3.6.1-management 55 | # ports: 56 | # - "15672:15672" 57 | # - "5672:5672" 58 | -------------------------------------------------------------------------------- /test/fixtures/missing-docker-files/some-file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeareJH/workflow/bf4d11820ab8eccf1b8f7a7d168419d58131c5d6/test/fixtures/missing-docker-files/some-file -------------------------------------------------------------------------------- /test/fixtures/test-env-files/some-file.php: -------------------------------------------------------------------------------- 1 | some-file.php -------------------------------------------------------------------------------- /test/fixtures/test-env/app.php.dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Michael Woodward 3 | 4 | WORKDIR /var/www 5 | RUN chown www-data:www-data /var/www 6 | 7 | CMD ["php-fpm"] 8 | 9 | -------------------------------------------------------------------------------- /test/fixtures/test-env/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | php: 5 | container_name: m2-php 6 | image: workflow-test 7 | build: 8 | context: ./ 9 | dockerfile: app.php.dockerfile 10 | working_dir: /var/www 11 | ports: 12 | - 9000 13 | -------------------------------------------------------------------------------- /test/fixtures/valid-env/.docker/local.env: -------------------------------------------------------------------------------- 1 | # Copy to local.env and store on local machine 2 | 3 | # Host Config 4 | MAGE_ROOT_DIR=/var/www 5 | MAGE_HOST=https://m2.dev 6 | MAGE_ADMIN_USER=admin 7 | MAGE_ADMIN_PASS=password123 8 | MAGE_ADMIN_FIRSTNAME=Joe 9 | MAGE_ADMIN_LASTNAME=Bloggs 10 | MAGE_ADMIN_EMAIL=magento@wearejh.com 11 | MAGE_BACKEND_FRONTNAME=admin 12 | HTTPS=on 13 | 14 | # MySQL Details 15 | MYSQL_ROOT_PASSWORD=docker 16 | MYSQL_DATABASE=docker 17 | MYSQL_USER=docker 18 | MYSQL_PASSWORD=docker 19 | 20 | # PHP 21 | PHP_MEMORY_LIMIT=2G 22 | 23 | # RabbitMQ 24 | RABBITMQ_DEFAULT_USER=user 25 | RABBITMQ_DEFAULT_PASS=password 26 | 27 | ## Mail config 28 | MAIL_HOST=mail 29 | MAIL_PORT=1025 30 | 31 | ## Xdebug config 32 | XDEBUG_IDE_KEY=PHPSTORM 33 | XDEBUG_CONFIG=remote_host=10.254.254.254 34 | XDEBUG_ENABLE=true 35 | 36 | # Blackfire 37 | BLACKFIRE_CLIENT_ID= 38 | BLACKFIRE_CLIENT_TOKEN= 39 | BLACKFIRE_SERVER_ID= 40 | BLACKFIRE_SERVER_TOKEN= -------------------------------------------------------------------------------- /test/fixtures/valid-env/app.php.dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.0-fpm 2 | MAINTAINER Michael Woodward 3 | 4 | ARG BUILD_ENV=dev 5 | ENV PROD_ENV=prod 6 | 7 | CMD ["php-fpm"] -------------------------------------------------------------------------------- /test/fixtures/valid-env/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx: 5 | volumes: 6 | - .docker/certs:/etc/letsencrypt 7 | env_file: 8 | - ./.docker/local.env 9 | 10 | php: 11 | env_file: 12 | - .docker/local.env 13 | volumes: 14 | - .docker/composer-cache:/var/www/.docker/composer-cache 15 | 16 | db: 17 | env_file: 18 | - ./.docker/local.env 19 | volumes: 20 | - .docker/db/:/docker-entrypoint-initdb.d/ 21 | 22 | # rabbitmq: 23 | # env_file: 24 | # - ./.docker/local.env 25 | 26 | mail: 27 | container_name: m2-mail 28 | image: schickling/mailcatcher 29 | ports: 30 | - 1025 31 | - 1080:1080 32 | 33 | blackfire: 34 | container_name: m2-blackfire 35 | image: blackfire/blackfire 36 | env_file: 37 | - .docker/local.env 38 | -------------------------------------------------------------------------------- /test/fixtures/valid-env/docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | app-var: 5 | 6 | services: 7 | nginx: 8 | env_file: 9 | - .docker/production.env 10 | 11 | php: 12 | env_file: 13 | - .docker/production.env 14 | volumes: 15 | - app-var:/var/www/var 16 | 17 | db: 18 | env_file: 19 | - .docker/production.env 20 | 21 | # rabbitmq: 22 | # env_file: 23 | # - ./.docker/production.env -------------------------------------------------------------------------------- /test/fixtures/valid-env/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | volumes: 4 | db-data: 5 | app-pub: 6 | app-var: 7 | app-env: 8 | 9 | services: 10 | haproxy: 11 | image: dockercloud/haproxy:latest 12 | links: 13 | - varnish 14 | environment: 15 | - CERT_FOLDER=/certs 16 | volumes: 17 | - /var/run/docker.sock:/var/run/docker.sock 18 | - .docker/certs:/certs 19 | ports: 20 | - "80:80" 21 | - "443:443" 22 | 23 | varnish: 24 | container_name: m2-varnish 25 | image: wearejh/magento-varnish:latest 26 | environment: 27 | - FORCE_SSL=yes 28 | depends_on: 29 | - nginx 30 | 31 | nginx: 32 | container_name: m2 33 | image: nginx:stable-alpine 34 | working_dir: /var/www 35 | volumes: 36 | - .docker/nginx/sites:/etc/nginx/conf.d 37 | volumes_from: 38 | - php 39 | depends_on: 40 | - php 41 | 42 | php: 43 | container_name: m2-php 44 | image: wearejh/m2 45 | build: 46 | context: ./ 47 | dockerfile: app.php.dockerfile 48 | volumes: 49 | - app-env:/var/www/app/etc 50 | - ~/.composer/auth.json:/root/.composer/auth.json 51 | working_dir: /var/www 52 | ports: 53 | - 9000 54 | 55 | db: 56 | container_name: m2-db 57 | image: mysql:5.6 58 | volumes: 59 | - db-data:/var/lib/mysql 60 | ports: 61 | - "3306:3306" 62 | restart: unless-stopped 63 | 64 | redis: 65 | container_name: m2-redis 66 | image: redis:3-alpine 67 | ports: 68 | - "6379:6379" 69 | 70 | # elasticsearch: 71 | # image: elasticsearch 72 | # ports: 73 | # - "9200:9200" 74 | # - "9300:9300" 75 | 76 | # rabbitmq: 77 | # image: rabbitmq:3.6.1-management 78 | # ports: 79 | # - "15672:15672" 80 | # - "5672:5672" 81 | -------------------------------------------------------------------------------- /test/fixtures/valid-env/some-file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeareJH/workflow/bf4d11820ab8eccf1b8f7a7d168419d58131c5d6/test/fixtures/valid-env/some-file.txt -------------------------------------------------------------------------------- /test/fixtures/valid-env/some-folder/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeareJH/workflow/bf4d11820ab8eccf1b8f7a7d168419d58131c5d6/test/fixtures/valid-env/some-folder/.gitkeep -------------------------------------------------------------------------------- /test/fixtures/valid-env/some-import.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeareJH/workflow/bf4d11820ab8eccf1b8f7a7d168419d58131c5d6/test/fixtures/valid-env/some-import.sql --------------------------------------------------------------------------------