From 88bb5b9f696485b2e3cc29d5aef542a2e688b424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20PLANCHAT?= Date: Wed, 5 Aug 2020 11:48:54 +0200 Subject: [PATCH 01/23] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e02d6fc..709fcff 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Installation While installing system-wide, you will need administrator privilleges to install the command inside `/usr/local/bin/` directory. ``` -sudo curl -L -o /usr/local/bin/kloud https://github.com/kiboko-labs/docker-images/releases/download/1.0.2/kloud.phar -sudo curl -L -o /usr/local/bin/kloud.pubkey https://github.com/kiboko-labs/docker-images/releases/download/1.0.2/kloud.phar.pubkey +sudo curl -L -o /usr/local/bin/kloud https://github.com/kiboko-labs/docker-images/releases/download/1.0.4/kloud.phar +sudo curl -L -o /usr/local/bin/kloud.pubkey https://github.com/kiboko-labs/docker-images/releases/download/1.0.4/kloud.phar.pubkey sudo chmod +x /usr/local/bin/kloud ``` From 60a37050b0678be734ab4fb809036b679892c584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20PLANCHAT?= Date: Wed, 5 Aug 2020 11:53:35 +0200 Subject: [PATCH 02/23] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 709fcff..7a1f636 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Installation While installing system-wide, you will need administrator privilleges to install the command inside `/usr/local/bin/` directory. ``` -sudo curl -L -o /usr/local/bin/kloud https://github.com/kiboko-labs/docker-images/releases/download/1.0.4/kloud.phar -sudo curl -L -o /usr/local/bin/kloud.pubkey https://github.com/kiboko-labs/docker-images/releases/download/1.0.4/kloud.phar.pubkey +sudo curl -L -o /usr/local/bin/kloud https://github.com/kiboko-labs/docker-images/releases/latest/download/kloud.phar +sudo curl -L -o /usr/local/bin/kloud.pubkey https://github.com/kiboko-labs/docker-images/releases/latest/download/kloud.phar.pubkey sudo chmod +x /usr/local/bin/kloud ``` @@ -32,8 +32,8 @@ sudo chmod +x /usr/local/bin/kloud While installing in your project, no administrator privilege is required, the phar package will be available in the `bin/` directory. ``` -curl -L -o bin/kloud.phar https://github.com/kiboko-labs/docker-images/releases/download/1.0.2/kloud.phar -curl -L -o bin/kloud.phar.pubkey https://github.com/kiboko-labs/docker-images/releases/download/1.0.2/kloud.phar.pubkey +curl -L -o bin/kloud.phar https://github.com/kiboko-labs/docker-images/releases/latest/download/kloud.phar +curl -L -o bin/kloud.phar.pubkey https://github.com/kiboko-labs/docker-images/releases/latest/download/kloud.phar.pubkey chmod +x bin/kloud.phar ``` From 9200f7de4069cdcc442d7422d53a4b0a16271fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Fri, 11 Sep 2020 20:55:00 +0200 Subject: [PATCH 03/23] Added Dejavu enable/disable switch Added ElasticStack enable/disable switch + integration Added Mac OS optimizations enable/disable switch Updated ElasticSearch image tags Added health checks for MySQL and PostgreSQL Fixed stack merging issues while the service order does not match Added the ability to self-manage some services and volumes in order to ignore stack updates --- Dockerfile | 2 +- README.md | 30 +++- bin/build | 7 +- bin/kloud | 4 + phpspec.yml | 6 +- .../Context/BuildableContextSpec.php | 32 ++-- .../Context/DependentBuildableContextSpec.php | 128 +++++++++++++++ .../Packaging/DependencyTree/NodeSpec.php | 18 +-- .../Stack/Compose/EnvironmentVariableSpec.php | 6 +- .../Stack/Compose/ExpressionParserSpec.php | 58 +++++++ .../Normalizer/ServiceDenormalizerSpec.php | 101 ++++++++++++ spec/Domain/Stack/Compose/PortMappingSpec.php | 6 +- spec/Domain/Stack/Compose/ServiceSpec.php | 18 +-- spec/Domain/Stack/Compose/VariableSpec.php | 4 +- .../Stack/Compose/VolumeMappingSpec.php | 4 +- spec/Domain/Stack/Compose/VolumeSpec.php | 4 +- spec/Domain/Stack/StackBuilderSpec.php | 42 +++++ .../Packaging/Context/BuildableContext.php | 2 +- src/Domain/Packaging/Context/Context.php | 2 +- .../Context/DependentBuildableContext.php | 2 +- .../Packaging/Context/DependentContext.php | 4 +- src/Domain/Packaging/Native/PHP/Tag.php | 47 +----- .../Packaging/Native/PHP/TagVariation.php | 39 +---- .../Packaging/Native/PostgreSQL/Package.php | 2 +- .../Packaging/Native/PostgreSQL/Tag.php | 47 +----- src/Domain/Packaging/Tag/TagTrait.php | 55 +++++++ .../Stack/Compose/EnvironmentVariable.php | 2 +- src/Domain/Stack/Compose/Expression.php | 38 +++++ src/Domain/Stack/Compose/ExpressionParser.php | 32 ++++ src/Domain/Stack/Compose/Label.php | 25 +++ .../Normalizer/ServiceDenormalizer.php | 60 ++++++- src/Domain/Stack/Compose/Service.php | 79 +++++++++ src/Domain/Stack/Compose/Stack.php | 44 +++-- src/Domain/Stack/ContextBuilder.php | 105 ++++++++++++ src/Domain/Stack/DTO/Context.php | 20 ++- src/Domain/Stack/DTO/Stack.php | 6 +- .../OroPlatform/Service/ElasticSearch.php | 34 ++-- .../Stack/OroPlatform/Service/MySQL.php | 15 +- .../Stack/OroPlatform/Service/Nginx.php | 150 +++++++----------- src/Domain/Stack/OroPlatform/Service/PHP.php | 37 +++-- .../Stack/OroPlatform/Service/PHP/CLI.php | 11 +- .../OroPlatform/Service/PHP/CLIWithXdebug.php | 14 +- .../Stack/OroPlatform/Service/PHP/DBGP.php | 7 +- .../Stack/OroPlatform/Service/PHP/FPM.php | 11 +- .../OroPlatform/Service/PHP/FPMWithXdebug.php | 11 +- .../Stack/OroPlatform/Service/PostgreSQL.php | 9 +- .../Console/Command/Stack/InitCommand.php | 37 ++++- .../Console/Command/Stack/UpgradeCommand.php | 63 ++++++-- src/Platform/Console/ContextWizard.php | 38 ++++- src/Platform/Console/ServicePrinter.php | 18 +-- src/Platform/Console/VolumePrinter.php | 17 +- .../MarelloCommunity.php | 20 ++- .../MarelloEnterprise.php | 20 ++- .../MiddlewareCommunity.php | 5 +- .../MiddlewareEnterprise.php | 5 +- .../OroCRMCommunity.php | 15 +- .../OroCRMEnterprise.php | 15 +- .../OroCommerceCommunity.php | 15 +- .../OroCommerceEnterprise.php | 15 +- .../OroPlatformCommunity.php | 25 ++- .../OroPlatformEnterprise.php | 25 ++- 61 files changed, 1281 insertions(+), 432 deletions(-) create mode 100644 spec/Domain/Packaging/Context/DependentBuildableContextSpec.php create mode 100644 spec/Domain/Stack/Compose/ExpressionParserSpec.php create mode 100644 spec/Domain/Stack/Compose/Normalizer/ServiceDenormalizerSpec.php create mode 100644 spec/Domain/Stack/StackBuilderSpec.php create mode 100644 src/Domain/Packaging/Tag/TagTrait.php create mode 100644 src/Domain/Stack/Compose/Expression.php create mode 100644 src/Domain/Stack/Compose/ExpressionParser.php create mode 100644 src/Domain/Stack/Compose/Label.php create mode 100644 src/Domain/Stack/ContextBuilder.php diff --git a/Dockerfile b/Dockerfile index 359f231..1787eae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,7 +57,7 @@ RUN set -ex\ perl \ pkgconf \ pkgconfig \ - python \ + python3 \ re2c \ readline \ sqlite-libs \ diff --git a/README.md b/README.md index 7a1f636..4a0f4aa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Docker images and stacks for Oro and Marello development This project aims at building your Docker stack for [OroCommerce](https://oroinc.com/b2b-ecommerce/), [OroCRM](https://oroinc.com/orocrm/), [OroPlatform](https://oroinc.com/oroplatform/) and [Marello](https://www.marello.com/) development. -> ⚠️ Nota: Those stacks are not suited for production hosting +> ⚠️ Nota: Those stacks are not suited for production hosting, but to provide an environment based on Docker as identical as possible to OroCloud on a personal computer. * [Installation](#installation) * [Usage](#usage) @@ -60,6 +60,32 @@ You can invoke the following command that will guess what you need and a wizard `kloud stack:init` +#### Available command line options + +* Database engine selection + * `--mysql`: Set up the application to use MySQL. + * `--postgresql`: Set up the application to use PostgreSQL. + +* Xdebug a debugger for PHP + * `--with-xdebug`: Set up the application to use Xdebug. + * `--without-xdebug`: Set up the application without Xdebug. + +* Blackfire an APM for PHP performance optimizations + * `--with-blackfire`: Set up the application to use Blackfire. + * `--without-blackfire`: Set up the application without Blackfire. + +* Dejavu, An ElasticSearch UI + * `--with-dejavu`: Set up the application to use Dejavu UI. + * `--without-dejavu`: Set up the application without Dejavu UI. + +* Elastic Stack for application logging + * `--with-elastic-stack`: Set up the application to use Elastic Stack logging. + * `--without-elastic-stack`: Set up the application without Elastic Stack logging. + +* Mac OS optimizations on data volumes + * `--with-macos-optimizations`: Set up the application to use Docker for Mac optimizations. + * `--without-macos-optimizations`: Set up the application without Docker for Mac optimizations. + ### Update your stack In an existing project, you can upgrade the docker stack configuration. automagically. @@ -73,6 +99,8 @@ If you need to build the images you need in a stack, you can execute the followi `kloud image:build` +To enable experimental images, you will need to add option `--with-experimental`. + ### Test the required images If you need to test if the images you are using are following every constraint you would expect: diff --git a/bin/build b/bin/build index 81c430a..a88169a 100755 --- a/bin/build +++ b/bin/build @@ -4,7 +4,10 @@ DIRECTORY=$( dirname $( dirname $0 ) ) EXEC="docker-compose --project-directory=$DIRECTORY/ --file=$DIRECTORY/docker-compose.yml exec sh" -$EXEC composer install --no-dev --optimize-autoloader \ +$EXEC composer install --dev --optimize-autoloader \ + && $EXEC vendor/bin/phpspec run --stop-on-failure --no-code-generation \ + && $EXEC composer install --no-dev --optimize-autoloader \ && $EXEC box build \ && docker build -t kiboko/kloud:latest $DIRECTORY/build/ \ - && docker push kiboko/kloud \ No newline at end of file + && docker push kiboko/kloud \ + && $EXEC composer install --dev --optimize-autoloader \ No newline at end of file diff --git a/bin/kloud b/bin/kloud index fac6e92..e5d5e24 100755 --- a/bin/kloud +++ b/bin/kloud @@ -1,6 +1,10 @@ #!/usr/bin/env php beConstructedWith(new Packaging\Context\Context(null, []), '/path/to/dockerfile', []); + $this->beConstructedWith( + new Packaging\Context\Context(null, []), + new Packaging\Placeholder('/path/to/dockerfile'), + [] + ); - $this->isParentBuildable()->shouldReturn(false); + $this->asBuildable()->shouldReturnAnInstanceOf(Packaging\Context\BuildableContextInterface::class); + } + + public function it_does_fail_if_empty_path() + { + $this->beConstructedWith(null, null, []); + + $this->shouldThrow(new \RuntimeException('Could not determine path from parent context, please provide a path for the context.')) + ->duringInstantiation(); } public function it_does_fail_if_empty_path_and_not_a_buildable_parent() { $this->beConstructedWith(new Packaging\Context\Context(null, []), null, []); - $this->shouldThrow(new \RuntimeException('Could not determine path from parent context.')) + $this->shouldThrow(new \RuntimeException('Could not determine path from parent context, please provide a path for the context.')) ->duringInstantiation(); } @@ -37,23 +49,23 @@ public function it_does_have_a_buildable_parent() { $this->beConstructedWith(new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), null, []); - $this->isParentBuildable()->shouldReturn(true); + $this->asBuildable()->shouldReturnAnInstanceOf(Packaging\Context\BuildableContextInterface::class); } public function it_does_build_a_copy_from_parent() { $this->beConstructedWith(new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), null, []); - $this->getParent(['%dolor%' => 'sit amet'])->shouldReturnAnInstanceOf(Packaging\Context\ContextInterface::class); + $this->getParent()->shouldReturnAnInstanceOf(Packaging\Context\ContextInterface::class); } public function it_does_build_a_copy_from_parent_and_combine_variables() { $this->beConstructedWith(new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), null, []); - $this->getParent(['%dolor%' => 'sit amet'])->shouldIterateAs(new \ArrayObject([ - '%lorem%' => 'ipsum', + $this->getBuildableParent(null, ['%dolor%' => 'sit amet'])->shouldIterateAs(new \ArrayObject([ '%dolor%' => 'sit amet', + '%lorem%' => 'ipsum', ])); } @@ -61,7 +73,7 @@ public function it_does_build_a_copy_from_parent_and_replace_variables() { $this->beConstructedWith(new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), null, []); - $this->getParent(['%lorem%' => 'ipsum sit amet'])->shouldIterateAs(new \ArrayObject([ + $this->getBuildableParent(null, ['%lorem%' => 'ipsum sit amet'])->shouldIterateAs(new \ArrayObject([ '%lorem%' => 'ipsum sit amet', ])); } diff --git a/spec/Domain/Packaging/Context/DependentBuildableContextSpec.php b/spec/Domain/Packaging/Context/DependentBuildableContextSpec.php new file mode 100644 index 0000000..e9a9026 --- /dev/null +++ b/spec/Domain/Packaging/Context/DependentBuildableContextSpec.php @@ -0,0 +1,128 @@ +beConstructedWith( + null, + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->shouldHaveType(Packaging\Context\DependentBuildableContext::class); + $this->shouldHaveType(Packaging\Context\ContextInterface::class); + $this->shouldHaveType(Packaging\Context\BuildableContextInterface::class); + } + + public function it_does_not_have_a_buildable_parent() + { + $this->beConstructedWith( + new Packaging\Context\Context(null, []), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + new Packaging\Placeholder('/path/to/dockerfile'), + [] + ); + + $this->asBuildable()->shouldReturnAnInstanceOf(Packaging\Context\BuildableContextInterface::class); + } + + public function it_does_fail_if_empty_path() + { + $this->beConstructedWith( + null, + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->shouldThrow(new \RuntimeException('Could not determine path from parent context, please provide a path for the context.')) + ->duringInstantiation(); + } + + public function it_does_fail_if_empty_path_and_not_a_buildable_parent() + { + $this->beConstructedWith( + new Packaging\Context\Context(null, []), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->shouldThrow(new \RuntimeException('Could not determine path from parent context, please provide a path for the context.')) + ->duringInstantiation(); + } + + public function it_does_have_a_buildable_parent() + { + $this->beConstructedWith( + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->asBuildable()->shouldReturnAnInstanceOf(Packaging\Context\BuildableContextInterface::class); + } + + public function it_does_build_a_copy_from_parent() + { + $this->beConstructedWith( + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->getParent()->shouldReturnAnInstanceOf(Packaging\Context\ContextInterface::class); + } + + public function it_does_build_a_copy_from_parent_and_combine_variables() + { + $this->beConstructedWith( + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->getBuildableParent(null, ['%dolor%' => 'sit amet'])->shouldIterateAs(new \ArrayObject([ + '%dolor%' => 'sit amet', + '%lorem%' => 'ipsum', + ])); + } + + public function it_does_build_a_copy_from_parent_and_replace_variables() + { + $this->beConstructedWith( + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->getBuildableParent(null, ['%lorem%' => 'ipsum sit amet'])->shouldIterateAs(new \ArrayObject([ + '%lorem%' => 'ipsum sit amet', + ])); + } + + public function it_does_copy_path_from_parent() + { + $this->beConstructedWith( + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), ['%lorem%' => 'ipsum']), + new Packaging\Context\BuildableContext(null, new Packaging\Placeholder('/path/to/dockerfile'), []), + null, + [] + ); + + $this->getPath()->shouldBeLike('/path/to/dockerfile'); + } +} diff --git a/spec/Domain/Packaging/DependencyTree/NodeSpec.php b/spec/Domain/Packaging/DependencyTree/NodeSpec.php index 6befd27..aeb832c 100644 --- a/spec/Domain/Packaging/DependencyTree/NodeSpec.php +++ b/spec/Domain/Packaging/DependencyTree/NodeSpec.php @@ -1,14 +1,14 @@ parse('LOREM_IPSUM') + ->shouldBeLike(new Expression('LOREM_IPSUM')); + } + + function it_parses_a_simple_variable() + { + $this->parse('$LOREM_IPSUM') + ->shouldBeLike(new Expression(new Variable('LOREM_IPSUM'))); + } + + function it_parses_a_simple_variable_with_prefix() + { + $this->parse('test:$LOREM_IPSUM') + ->shouldBeLike(new Expression('test:', new Variable('LOREM_IPSUM'))); + } + + function it_parses_a_simple_variable_with_suffix() + { + $this->parse('$LOREM_IPSUM:test') + ->shouldBeLike(new Expression(new Variable('LOREM_IPSUM'), ':test')); + } + + function it_parses_a_complex_variable() + { + $this->parse('${LOREM_IPSUM}') + ->shouldBeLike(new Expression(new Variable('LOREM_IPSUM'))); + } + + function it_parses_a_complex_variable_with_prefix() + { + $this->parse('test-${LOREM_IPSUM}') + ->shouldBeLike(new Expression('test-', new Variable('LOREM_IPSUM'))); + } + + function it_parses_a_complex_variable_with_suffix() + { + $this->parse('${LOREM_IPSUM}-test') + ->shouldBeLike(new Expression(new Variable('LOREM_IPSUM'), '-test')); + } + + function it_parses_two_complex_variables() + { + $this->parse('${LOREM_IPSUM}:${DOLOR}') + ->shouldBeLike(new Expression(new Variable('LOREM_IPSUM'), ':', new Variable('DOLOR'))); + } +} \ No newline at end of file diff --git a/spec/Domain/Stack/Compose/Normalizer/ServiceDenormalizerSpec.php b/spec/Domain/Stack/Compose/Normalizer/ServiceDenormalizerSpec.php new file mode 100644 index 0000000..d2c857c --- /dev/null +++ b/spec/Domain/Stack/Compose/Normalizer/ServiceDenormalizerSpec.php @@ -0,0 +1,101 @@ +denormalize( + [ + 'image' => 'foo', + 'environment' => [ + 'LOREM_IPSUM', + ], + ], + Service::class, + 'yaml', + [ + 'stack_service_name' => 'lorem_ipsum', + ], + )->shouldBeLike( + (new Service('lorem_ipsum', 'foo')) + ->addEnvironmentVariables( + new InheritedEnvironmentVariable(new Variable('LOREM_IPSUM')) + ), + ); + } + + function it_detects_environment_variables_assignment() + { + $this->denormalize( + [ + 'image' => 'foo', + 'environment' => [ + 'LOREM_IPSUM=1234', + ], + ], + Service::class, + 'yaml', + [ + 'stack_service_name' => 'lorem_ipsum', + ], + )->shouldBeLike( + (new Service('lorem_ipsum', 'foo')) + ->addEnvironmentVariables( + new EnvironmentVariable(new Variable('LOREM_IPSUM'), '1234') + ), + ); + } + + function it_detects_environment_variables_assignment_from_variable() + { + $this->denormalize( + [ + 'image' => 'foo', + 'environment' => [ + 'LOREM_IPSUM=$DOLOR', + ], + ], + Service::class, + 'yaml', + [ + 'stack_service_name' => 'lorem_ipsum', + ], + )->shouldBeLike( + (new Service('lorem_ipsum', 'foo')) + ->addEnvironmentVariables( + new EnvironmentVariable(new Variable('LOREM_IPSUM'), new Expression(new Variable('DOLOR'))) + ), + ); + } + + function it_detects_environment_variables_assignment_from_expression() + { + $this->denormalize( + [ + 'image' => 'foo', + 'environment' => [ + 'LOREM_IPSUM=${DOLOR}-32', + ], + ], + Service::class, + 'yaml', + [ + 'stack_service_name' => 'lorem_ipsum', + ], + )->shouldBeLike( + (new Service('lorem_ipsum', 'foo')) + ->addEnvironmentVariables( + new EnvironmentVariable(new Variable('LOREM_IPSUM'), new Expression(new Variable('DOLOR'), '-32')) + ), + ); + } +} \ No newline at end of file diff --git a/spec/Domain/Stack/Compose/PortMappingSpec.php b/spec/Domain/Stack/Compose/PortMappingSpec.php index 7327237..f1cf16f 100644 --- a/spec/Domain/Stack/Compose/PortMappingSpec.php +++ b/spec/Domain/Stack/Compose/PortMappingSpec.php @@ -1,9 +1,9 @@ beConstructedWith('lorem', 'kiboko/php:kloud'); - $this->getImage()->shouldReturn('kiboko/php:kloud'); + $this->beConstructedWith('lorem', 'kiboko/kloud'); + $this->getImage()->shouldReturn('kiboko/kloud'); } function it_is_failing_normalization_without_an_image_or_a_build_context(NormalizerInterface $normalizer) { $this->beConstructedWith('lorem'); - $this->shouldThrow(new \RuntimeException('The service should have either an image or a build context declared.')) + $this->shouldThrow(new \RuntimeException('The service should have either an image, a build context declared or extend an existing service.')) ->during('normalize', [$normalizer]); } diff --git a/spec/Domain/Stack/Compose/VariableSpec.php b/spec/Domain/Stack/Compose/VariableSpec.php index 6db4d13..2a2c0b8 100644 --- a/spec/Domain/Stack/Compose/VariableSpec.php +++ b/spec/Domain/Stack/Compose/VariableSpec.php @@ -1,8 +1,8 @@ denormalize( + $config, + Stack\Compose\Stack::class, + 'yaml', + [] + ); + + $this->beConstructedWith(new Stack\OroPlatform\Service\PHP('/path/to/stacks')); + } +} \ No newline at end of file diff --git a/src/Domain/Packaging/Context/BuildableContext.php b/src/Domain/Packaging/Context/BuildableContext.php index 3fe93e7..3deb58d 100644 --- a/src/Domain/Packaging/Context/BuildableContext.php +++ b/src/Domain/Packaging/Context/BuildableContext.php @@ -52,7 +52,7 @@ public function getBuildableParent(?string $path = null, array $variables = []): public function asBuildable(?string $path = null, array $variables = []): BuildableContextInterface { - return new self($this->parent, $path ?? $this->path, $variables); + return new self($this->parent, $path !== null ? new Placeholder($path) : $this->path, $variables + $this->getLocalArrayCopy()); } public function hasParent(): bool diff --git a/src/Domain/Packaging/Context/Context.php b/src/Domain/Packaging/Context/Context.php index 2711576..a53051e 100644 --- a/src/Domain/Packaging/Context/Context.php +++ b/src/Domain/Packaging/Context/Context.php @@ -32,7 +32,7 @@ public function getBuildableParent(?string $path = null, array $variables = []): public function asBuildable(?string $path = null, array $variables = []): BuildableContextInterface { - return new BuildableContext($this->parent, $path, $variables + $this->getLocalArrayCopy()); + return new BuildableContext($this->parent, $path !== null ? new Placeholder($path) : null, $variables + $this->getLocalArrayCopy()); } public function hasParent(): bool diff --git a/src/Domain/Packaging/Context/DependentBuildableContext.php b/src/Domain/Packaging/Context/DependentBuildableContext.php index 9206324..a02f1d8 100644 --- a/src/Domain/Packaging/Context/DependentBuildableContext.php +++ b/src/Domain/Packaging/Context/DependentBuildableContext.php @@ -55,7 +55,7 @@ public function getBuildableParent(?string $path = null, array $variables = []): public function asBuildable(?string $path = null, array $variables = []): BuildableContextInterface { - return new self($this->parent, $this->dependency, $path ?? $this->path, $variables); + return new self($this->parent, $this->dependency, $path !== null ? new Placeholder($path) : $this->path, $variables + $this->getLocalArrayCopy()); } public function hasParent(): bool diff --git a/src/Domain/Packaging/Context/DependentContext.php b/src/Domain/Packaging/Context/DependentContext.php index f8dd4b7..fbfdfe9 100644 --- a/src/Domain/Packaging/Context/DependentContext.php +++ b/src/Domain/Packaging/Context/DependentContext.php @@ -4,6 +4,8 @@ namespace Kiboko\Cloud\Domain\Packaging\Context; +use Kiboko\Cloud\Domain\Packaging\Placeholder; + final class DependentContext implements ContextInterface, \IteratorAggregate { use ContextTrait; @@ -36,7 +38,7 @@ public function getBuildableParent(?string $path = null, array $variables = []): public function asBuildable(?string $path = null, array $variables = []): BuildableContextInterface { - return new DependentBuildableContext($this->parent, $this->dependency, $path, $variables + $this->getLocalArrayCopy()); + return new DependentBuildableContext($this->parent, $this->dependency, $path !== null ? new Placeholder($path) : null, $variables + $this->getLocalArrayCopy()); } public function hasParent(): bool diff --git a/src/Domain/Packaging/Native/PHP/Tag.php b/src/Domain/Packaging/Native/PHP/Tag.php index d021c63..2df904a 100644 --- a/src/Domain/Packaging/Native/PHP/Tag.php +++ b/src/Domain/Packaging/Native/PHP/Tag.php @@ -6,9 +6,7 @@ final class Tag implements Packaging\Tag\TagBuildInterface { - private Packaging\RepositoryInterface $repository; - private Packaging\Placeholder $name; - private Packaging\Context\BuildableContextInterface $context; + use Packaging\Tag\TagTrait; public function __construct( Packaging\RepositoryInterface $repository, @@ -18,47 +16,4 @@ public function __construct( $this->name = new Packaging\Placeholder('%php.version%-%php.flavor%', $context->getArrayCopy()); $this->context = $context; } - - public function getContext(): Packaging\Context\ContextInterface - { - return $this->context; - } - - public function __toString() - { - return (string) $this->name; - } - - public function getRepository(): Packaging\RepositoryInterface - { - return $this->repository; - } - - public function pull(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Pull($this)); - - return $task; - } - - public function push(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Push($this)); - - return $task; - } - - public function build(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Build($this, $this->context)); - - return $task; - } - - public function forceBuild(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\ForceBuild($this, $this->context)); - - return $task; - } } \ No newline at end of file diff --git a/src/Domain/Packaging/Native/PHP/TagVariation.php b/src/Domain/Packaging/Native/PHP/TagVariation.php index 71f22be..bda603b 100644 --- a/src/Domain/Packaging/Native/PHP/TagVariation.php +++ b/src/Domain/Packaging/Native/PHP/TagVariation.php @@ -6,9 +6,7 @@ final class TagVariation implements Packaging\Tag\TagBuildInterface, Packaging\Tag\DependentTagInterface { - private Packaging\RepositoryInterface $repository; - private Packaging\Placeholder $name; - private Packaging\Context\BuildableContextInterface $context; + use Packaging\Tag\TagTrait; private Packaging\Tag\TagInterface $from; public function __construct( @@ -22,43 +20,8 @@ public function __construct( $this->from = $from; } - public function getContext(): Packaging\Context\ContextInterface - { - return $this->context; - } - public function getParent(): Packaging\Tag\TagInterface { return $this->from; } - - public function __toString() - { - return (string) $this->name; - } - - public function getRepository(): Packaging\RepositoryInterface - { - return $this->repository; - } - - public function pull(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - return $task->then(new Packaging\Execution\Command\Pull($this)); - } - - public function push(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - return $task->then(new Packaging\Execution\Command\Push($this)); - } - - public function build(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - return $task->then(new Packaging\Execution\Command\BuildFrom($this, $this->from, $this->context)); - } - - public function forceBuild(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - return $task->then(new Packaging\Execution\Command\ForceBuildFrom($this, $this->from, $this->context)); - } } \ No newline at end of file diff --git a/src/Domain/Packaging/Native/PostgreSQL/Package.php b/src/Domain/Packaging/Native/PostgreSQL/Package.php index 2d2d319..6c01ceb 100644 --- a/src/Domain/Packaging/Native/PostgreSQL/Package.php +++ b/src/Domain/Packaging/Native/PostgreSQL/Package.php @@ -39,7 +39,7 @@ public function getIterator() { /** @var Packaging\Context\BuildableContextInterface $context */ foreach ($this() as $context) { - yield new Native\PHP\Tag($this->repository, $context); + yield new Native\PostgreSQL\Tag($this->repository, $context); } } diff --git a/src/Domain/Packaging/Native/PostgreSQL/Tag.php b/src/Domain/Packaging/Native/PostgreSQL/Tag.php index 29f81f5..c3242d8 100644 --- a/src/Domain/Packaging/Native/PostgreSQL/Tag.php +++ b/src/Domain/Packaging/Native/PostgreSQL/Tag.php @@ -6,9 +6,7 @@ final class Tag implements Packaging\Tag\TagBuildInterface { - private Packaging\RepositoryInterface $repository; - private Packaging\Placeholder $name; - private Packaging\Context\BuildableContextInterface $context; + use Packaging\Tag\TagTrait; public function __construct( Packaging\RepositoryInterface $repository, @@ -18,47 +16,4 @@ public function __construct( $this->name = new Packaging\Placeholder('%postgresql.version%', $context->getArrayCopy()); $this->context = $context; } - - public function getContext(): Packaging\Context\ContextInterface - { - return $this->context; - } - - public function __toString() - { - return (string) $this->name; - } - - public function getRepository(): Packaging\RepositoryInterface - { - return $this->repository; - } - - public function pull(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Pull($this)); - - return $task; - } - - public function push(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Push($this)); - - return $task; - } - - public function build(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\Build($this, $this->context)); - - return $task; - } - - public function forceBuild(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task - { - $task->then(new Packaging\Execution\Command\ForceBuild($this, $this->context)); - - return $task; - } } \ No newline at end of file diff --git a/src/Domain/Packaging/Tag/TagTrait.php b/src/Domain/Packaging/Tag/TagTrait.php new file mode 100644 index 0000000..e7fb387 --- /dev/null +++ b/src/Domain/Packaging/Tag/TagTrait.php @@ -0,0 +1,55 @@ +context; + } + + public function __toString() + { + return (string) $this->name; + } + + public function getRepository(): Packaging\RepositoryInterface + { + return $this->repository; + } + + public function pull(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task + { + $task->then(new Packaging\Execution\Command\Pull($this)); + + return $task; + } + + public function push(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task + { + $task->then(new Packaging\Execution\Command\Push($this)); + + return $task; + } + + public function build(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task + { + $task->then(new Packaging\Execution\Command\Build($this, $this->context)); + + return $task; + } + + public function forceBuild(Packaging\Execution\CommandBus\Task $task): Packaging\Execution\CommandBus\Task + { + $task->then(new Packaging\Execution\Command\ForceBuild($this, $this->context)); + + return $task; + } +} \ No newline at end of file diff --git a/src/Domain/Stack/Compose/EnvironmentVariable.php b/src/Domain/Stack/Compose/EnvironmentVariable.php index 1a3e292..5b92ca3 100644 --- a/src/Domain/Stack/Compose/EnvironmentVariable.php +++ b/src/Domain/Stack/Compose/EnvironmentVariable.php @@ -5,7 +5,7 @@ final class EnvironmentVariable implements \Stringable, ValuedEnvironmentVariableInterface { private Variable $variable; - /** @var string|int */ + /** @var string|int|Variable|Expression */ private $value; public function __construct(Variable $variable, $value = null) diff --git a/src/Domain/Stack/Compose/Expression.php b/src/Domain/Stack/Compose/Expression.php new file mode 100644 index 0000000..e3485b2 --- /dev/null +++ b/src/Domain/Stack/Compose/Expression.php @@ -0,0 +1,38 @@ + $elements + */ + public function __construct(...$elements) + { + $this->elements = $elements; + } + + public function __toString() + { + return implode('', array_map(function ($item) { + return (string) $item; + }, $this->elements)); + } + + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + { + return (string) $this; + } + + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + { + // TODO: Implement denormalize() method. + } +} \ No newline at end of file diff --git a/src/Domain/Stack/Compose/ExpressionParser.php b/src/Domain/Stack/Compose/ExpressionParser.php new file mode 100644 index 0000000..869e75f --- /dev/null +++ b/src/Domain/Stack/Compose/ExpressionParser.php @@ -0,0 +1,32 @@ + 0) { + $elements[] = $matches[1]; + } + + if (isset($matches[3])) { + $elements[] = new Variable($matches[3]); + } else if (isset($matches[2])) { + $elements[] = new Variable($matches[2]); + } + + $offset += strlen($matches[0]); + } + + return new Expression(...$elements); + } +} \ No newline at end of file diff --git a/src/Domain/Stack/Compose/Label.php b/src/Domain/Stack/Compose/Label.php new file mode 100644 index 0000000..632835c --- /dev/null +++ b/src/Domain/Stack/Compose/Label.php @@ -0,0 +1,25 @@ +key = $key; + $this->value = $value; + } + + public function getKey(): string + { + return $this->key; + } + + public function getValue(): string + { + return $this->value; + } +} \ No newline at end of file diff --git a/src/Domain/Stack/Compose/Normalizer/ServiceDenormalizer.php b/src/Domain/Stack/Compose/Normalizer/ServiceDenormalizer.php index b75dbe7..191a7ef 100644 --- a/src/Domain/Stack/Compose/Normalizer/ServiceDenormalizer.php +++ b/src/Domain/Stack/Compose/Normalizer/ServiceDenormalizer.php @@ -3,7 +3,9 @@ namespace Kiboko\Cloud\Domain\Stack\Compose\Normalizer; use Kiboko\Cloud\Domain\Stack\Compose\EnvironmentVariable; +use Kiboko\Cloud\Domain\Stack\Compose\ExpressionParser; use Kiboko\Cloud\Domain\Stack\Compose\InheritedEnvironmentVariable; +use Kiboko\Cloud\Domain\Stack\Compose\Label; use Kiboko\Cloud\Domain\Stack\Compose\PortMapping; use Kiboko\Cloud\Domain\Stack\Compose\Service; use Kiboko\Cloud\Domain\Stack\Compose\Variable; @@ -12,6 +14,13 @@ final class ServiceDenormalizer implements DenormalizerInterface { + private ExpressionParser $expressionParser; + + public function __construct() + { + $this->expressionParser = new ExpressionParser(); + } + public function supportsDenormalization($data, string $type, string $format = null) { return $type === Service::class; @@ -19,6 +28,10 @@ public function supportsDenormalization($data, string $type, string $format = nu public function denormalize($data, string $type, string $format = null, array $context = []) { + if (!isset($context['stack_service_name'])) { + throw new \InvalidArgumentException('The context should contain key "stack_service_name" in order to build the service properly.'); + } + $service = new Service($context['stack_service_name'], $data['image'] ?? null); if (isset($data['build']['context'])) { @@ -40,7 +53,7 @@ public function denormalize($data, string $type, string $format = null, array $c } $service->addPorts(...array_map(function(string $ports) { - if (!preg_match('/^(?:(\d+)|\$\{([a-zA-Z0-9_]+)\}):(?:(\d+)|\$\{([a-zA-Z0-9_]+)\})$/', $ports, $matches)) { + if (!preg_match('/^(?:(\d+)|\$\{([a-zA-Z0-9_]+)\})(?::(?:(\d+)|\$\{([a-zA-Z0-9_]+)\}))?$/', $ports, $matches)) { throw new \InvalidArgumentException('The port mapping specification is invalid.'); } @@ -57,7 +70,7 @@ public function denormalize($data, string $type, string $format = null, array $c } else if (!empty($matches[4])) { $in = new Variable($matches[4]); } else { - throw new \RuntimeException('Cound not match any input port.'); + $in = null; } return new PortMapping($out, $in); @@ -66,14 +79,17 @@ public function denormalize($data, string $type, string $format = null, array $c if (isset($data['environment'])) { foreach ($data['environment'] as $variable => $value) { if (is_int($variable)) { - if (!preg_match('/^(?:([^=]+)=(.*)|(.*))$/', $value, $matches)) { + if (!preg_match('/^(?:([^=]+)=(.*)|([^=]+))$/', $value, $matches)) { throw new \RuntimeException(strtr('Invalid environment variable format: "%value%".', ['%value%' => $value])); } - if (isset($matches[1]) && !empty($matches[1])) { - $service->addEnvironmentVariables(new EnvironmentVariable(new Variable($matches[1]), $matches[2])); - } else { + if (isset($matches[3])) { $service->addEnvironmentVariables(new InheritedEnvironmentVariable(new Variable($matches[3]))); + } else if (isset($matches[2])) { + $service->addEnvironmentVariables(new EnvironmentVariable(new Variable($matches[1]), $this->expressionParser->parse($matches[2]))); + } else { + var_dump($matches); + throw new \RuntimeException(strtr('Invalid environment variable format: "%value%".', ['%value%' => $value])); } } else if (!empty($value)) { $service->addEnvironmentVariables(new EnvironmentVariable(new Variable($variable), $value)); @@ -115,6 +131,38 @@ public function denormalize($data, string $type, string $format = null, array $c } } + if (isset($data['healthcheck'])) { + if (isset($data['healthcheck']['test'])) { + if (is_string($data['healthcheck']['test'])) { + $service->setHealthCheckShellCommand($data['healthcheck']['test']); + } else if (is_array($data['healthcheck']['test'])) { + if ($data['healthcheck']['test'][0] === 'CMD') { + $service->setHealthCheckCommand(...array_slice($data['healthcheck']['test'], 1)); + } else if ($data['healthcheck']['test'][0] === 'CMD-SHELL') { + $service->setHealthCheckShellCommand(...array_slice($data['healthcheck']['test'], 1)); + } + } + } + if (isset($data['healthcheck']['interval'])) { + $service->setHealthCheckInterval($data['healthcheck']['interval']); + } + if (isset($data['healthcheck']['retries'])) { + $service->setHealthCheckRetries($data['healthcheck']['retries']); + } + if (isset($data['healthcheck']['timeout'])) { + $service->setHealthCheckTimeout($data['healthcheck']['timeout']); + } + if (isset($data['healthcheck']['start_period'])) { + $service->setHealthCheckStartPeriod($data['healthcheck']['start_period']); + } + } + + if (isset($data['labels']) && is_array($data['labels'])) { + $service->addLabels(...array_map(function (string $key, string $value) { + return new Label($key, $value); + }, array_keys($data['labels']), array_values($data['labels']))); + } + return $service; } } \ No newline at end of file diff --git a/src/Domain/Stack/Compose/Service.php b/src/Domain/Stack/Compose/Service.php index 51143c8..24c1ec0 100644 --- a/src/Domain/Stack/Compose/Service.php +++ b/src/Domain/Stack/Compose/Service.php @@ -23,6 +23,8 @@ final class Service implements NormalizableInterface private ?array $buildArguments; private array $dependencies; private array $command; + private array $healthCheck; + private array $labels; public function __construct(string $name, ?string $image = null, array $command = []) { @@ -39,6 +41,8 @@ public function __construct(string $name, ?string $image = null, array $command $this->buildArguments = []; $this->dependencies = []; $this->command = $command; + $this->healthCheck = []; + $this->labels = []; } public function getName(): string @@ -143,6 +147,69 @@ public function addVolumeMappings(VolumeMapping ...$volumes): self return $this; } + public function unsetHealthCheck(): self + { + $this->healthCheck = []; + + return $this; + } + + public function disableHealthCheck(): self + { + $this->healthCheck = ['disable' => true]; + + return $this; + } + + public function setHealthCheckCommand(string ...$parts): self + { + $this->healthCheck['test'] = ['CMD', ...$parts]; + + return $this; + } + + public function setHealthCheckShellCommand(string $shellCommand): self + { + $this->healthCheck['test'] = ['CMD-SHELL', $shellCommand]; + + return $this; + } + + public function setHealthCheckInterval(string $interval): self + { + $this->healthCheck['interval'] = $interval; + + return $this; + } + + public function setHealthCheckTimeout(string $timeout): self + { + $this->healthCheck['timeout'] = $timeout; + + return $this; + } + + public function setHealthCheckRetries(int $retries): self + { + $this->healthCheck['retries'] = $retries; + + return $this; + } + + public function setHealthCheckStartPeriod(string $startPeriod): self + { + $this->healthCheck['start_period'] = $startPeriod; + + return $this; + } + + public function addLabels(Label ...$labels): self + { + array_push($this->labels, ...$labels); + + return $this; + } + public function setNoRestart(): self { $this->restartMode = 'no'; @@ -244,6 +311,18 @@ public function normalize(NormalizerInterface $normalizer, string $format = null $configuration['command'] = $this->command; } + if (count($this->healthCheck) > 0) { + $configuration['healthcheck'] = $this->healthCheck; + } + + if (count($this->labels) > 0) { + $configuration['labels'] = iterator_to_array((function (Label ...$labels) { + foreach ($labels as $label) { + yield $label->getKey() => $label->getValue(); + } + })(...$this->labels)); + } + return array_merge( $configuration, [ diff --git a/src/Domain/Stack/Compose/Stack.php b/src/Domain/Stack/Compose/Stack.php index 4a47574..aa1b230 100644 --- a/src/Domain/Stack/Compose/Stack.php +++ b/src/Domain/Stack/Compose/Stack.php @@ -39,14 +39,21 @@ public function removeServices(Service ...$services): self public function replaceServices(Service ...$services): self { foreach ($services as $service) { - $this->removeServices(...$this->extractServices($service->getName())); + $this->replaceService($service); } - $this->addServices(...$services); - return $this; } + private function replaceService(Service $replacement): void + { + foreach ($this->services as &$service) { + if ($service->getName() === $replacement->getName()) { + $service = $replacement; + } + } + } + public function getServices(): array { return $this->services; @@ -57,9 +64,15 @@ public function getServices(): array */ public function extractServices(string ...$names): array { - return array_filter($this->services, function (Service $service) use ($names) { + $list = array_filter($this->services, function (Service $service) use ($names) { return in_array($service->getName(), $names, true); }); + + usort($list, function(Service $left, Service $right) use ($names) { + return (array_search($left->getName(), $names, true) <=> array_search($right->getName(), $names, true)); + }); + + return $list; } public function addVolumes(Volume ...$volumes): self @@ -71,7 +84,7 @@ public function addVolumes(Volume ...$volumes): self public function removeVolumes(Volume ...$volumes): self { - $this->services = array_filter($this->volumes, function (Volume $volume) use ($volumes) { + $this->volumes = array_filter($this->volumes, function (Volume $volume) use ($volumes) { return !in_array($volume, $volumes, true); }); @@ -81,14 +94,21 @@ public function removeVolumes(Volume ...$volumes): self public function replaceVolumes(Volume ...$volumes): self { foreach ($volumes as $volume) { - $this->removeVolumes(...$this->extractVolumes($volume->getName())); + $this->replaceVolume($volume); } - $this->addVolumes(...$volumes); - return $this; } + private function replaceVolume(Volume $replacement): void + { + foreach ($this->volumes as &$volume) { + if ($volume->getName() === $replacement->getName()) { + $volume = $replacement; + } + } + } + public function getVolumes(): array { return $this->volumes; @@ -99,9 +119,15 @@ public function getVolumes(): array */ public function extractVolumes(string ...$names): array { - return array_filter($this->volumes, function (Volume $volume) use ($names) { + $list = array_filter($this->volumes, function (Volume $volume) use ($names) { return in_array($volume->getName(), $names, true); }); + + usort($list, function(Volume $left, Volume $right) use ($names) { + return (array_search($left->getName(), $names, true) <=> array_search($right->getName(), $names, true)); + }); + + return $list; } public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) diff --git a/src/Domain/Stack/ContextBuilder.php b/src/Domain/Stack/ContextBuilder.php new file mode 100644 index 0000000..c36ed17 --- /dev/null +++ b/src/Domain/Stack/ContextBuilder.php @@ -0,0 +1,105 @@ +phpVersion = $phpVersion; + $this->selfManagedServices = []; + $this->selfManagedVolumes = []; + } + + public function getContext(): Context + { + $context = new Context($this->phpVersion, $this->application, $this->applicationVersion, $this->dbms, $this->isEnterpriseEdition); + $context->withBlackfire = $this->withBlackfire; + $context->withXdebug = $this->withXdebug; + $context->withDejavu = $this->withDejavu; + $context->withElasticStack = $this->withElasticStack; + $context->withDockerForMacOptimizations = $this->withDockerForMacOptimizations; + + return $context; + } + + public function withBlackfire(?bool $withBlackfire): self + { + $this->withBlackfire = $withBlackfire; + + return $this; + } + + public function withXdebug(?bool $withXdebug): self + { + $this->withXdebug = $withXdebug; + + return $this; + } + + public function withDejavu(?bool $withDejavu): self + { + $this->withDejavu = $withDejavu; + + return $this; + } + + public function withElasticStack(?bool $withElasticStack): self + { + $this->withElasticStack = $withElasticStack; + + return $this; + } + + public function setDbms(?string $dbms): self + { + $this->dbms = $dbms; + + return $this; + } + + public function setApplication(?string $application, ?string $applicationVersion, ?bool $isEnterpriseEdition): self + { + $this->application = $application; + $this->applicationVersion = $applicationVersion; + $this->isEnterpriseEdition = $isEnterpriseEdition; + + return $this; + } + + public function withDockerForMacOptimizations(?bool $withDockerForMacOptimizations): self + { + $this->withDockerForMacOptimizations = $withDockerForMacOptimizations; + + return $this; + } + + public function addSelfManagedService(string ...$serviceNames): self + { + array_push($this->selfManagedServices, $serviceNames); + + return $this; + } + + public function addSelfManagedVolumes(string ...$volumeNames): self + { + array_push($this->selfManagedVolumes, $volumeNames); + + return $this; + } +} \ No newline at end of file diff --git a/src/Domain/Stack/DTO/Context.php b/src/Domain/Stack/DTO/Context.php index bcbc74d..b5ba27f 100644 --- a/src/Domain/Stack/DTO/Context.php +++ b/src/Domain/Stack/DTO/Context.php @@ -8,29 +8,37 @@ final class Context const DBMS_MYSQL = 'mysql'; public string $phpVersion; - public ?bool $withBlackfire; - public ?bool $withXdebug; public ?string $dbms; public ?string $application; public ?string $applicationVersion; public ?bool $isEnterpriseEdition; + public ?bool $withBlackfire; + public ?bool $withXdebug; + public ?bool $withDejavu; + public ?bool $withElasticStack; + public ?bool $withDockerForMacOptimizations; + public array $selfManagedServices; + public array $selfManagedVolumes; public function __construct( string $phpVersion, ?string $application = null, ?string $applicationVersion = null, ?string $dbms = self::DBMS_POSTGRESQL, - ?bool $withBlackfire = null, - ?bool $withXdebug = null, ?bool $isEnterpriseEdition = false ) { $this->phpVersion = $phpVersion; - $this->withBlackfire = $withBlackfire; - $this->withXdebug = $withXdebug; $this->dbms = $dbms; $this->application = $application; $this->applicationVersion = $applicationVersion; $this->isEnterpriseEdition = $isEnterpriseEdition; + $this->withBlackfire = null; + $this->withXdebug = null; + $this->withDejavu = null; + $this->withElasticStack = null; + $this->withDockerForMacOptimizations = null; + $this->selfManagedServices = []; + $this->selfManagedVolumes = []; } public function getImagesRegex(): string diff --git a/src/Domain/Stack/DTO/Stack.php b/src/Domain/Stack/DTO/Stack.php index 9f13758..799af62 100644 --- a/src/Domain/Stack/DTO/Stack.php +++ b/src/Domain/Stack/DTO/Stack.php @@ -152,10 +152,10 @@ public function saveTo(string $path): void ); foreach ($this->files as $file) { - if (!file_exists($file->getDirectory())) { - mkdir($file->getDirectory(), 0755, true); + if (!file_exists($path . '/' . $file->getDirectory())) { + mkdir($path . '/' . $file->getDirectory(), 0755, true); } - file_put_contents($file->getPath(), $file->asBlob()); + file_put_contents($path . '/' . $file->getPath(), $file->asBlob()); } $stream = fopen($path . '.env.dist', 'w'); diff --git a/src/Domain/Stack/OroPlatform/Service/ElasticSearch.php b/src/Domain/Stack/OroPlatform/Service/ElasticSearch.php index fd03f83..ff71bbe 100644 --- a/src/Domain/Stack/OroPlatform/Service/ElasticSearch.php +++ b/src/Domain/Stack/OroPlatform/Service/ElasticSearch.php @@ -3,6 +3,7 @@ namespace Kiboko\Cloud\Domain\Stack\OroPlatform\Service; use Kiboko\Cloud\Domain\Stack\Compose\EnvironmentVariable; +use Kiboko\Cloud\Domain\Stack\Compose\Expression; use Kiboko\Cloud\Domain\Stack\Compose\PortMapping; use Kiboko\Cloud\Domain\Stack\Compose\Service; use Kiboko\Cloud\Domain\Stack\Compose\Variable; @@ -30,11 +31,11 @@ public function matches(DTO\Context $context): bool private function buildImageTag(DTO\Context $context) { if (Semver::satisfies($context->applicationVersion, '^3.0')) { - return 'docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.7'; + return 'docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.12'; } if (Semver::satisfies($context->applicationVersion, '^4.0')) { - return 'docker.elastic.co/elasticsearch/elasticsearch-oss:7.6.1'; + return 'docker.elastic.co/elasticsearch/elasticsearch-oss:7.9.1'; } throw new \RuntimeException('No image satisfies the application version constraint.'); @@ -45,15 +46,8 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack $stack->addServices( (new Service('elasticsearch', $this->buildImageTag($context))) ->addEnvironmentVariables( - new EnvironmentVariable(new Variable('cluster.name'), 'docker-cluster'), - new EnvironmentVariable(new Variable('bootstrap.memory_lock'), 'true'), new EnvironmentVariable(new Variable('ES_JAVA_OPTS'), '-Xms512m -Xmx512m'), - new EnvironmentVariable(new Variable('discovery.type'), 'single-node'), - new EnvironmentVariable(new Variable('http.port'), '9200'), - new EnvironmentVariable(new Variable('http.cors.allow-origin'), 'http://localhost:${DEJAVU_PORT},http://127.0.0.1:${DEJAVU_PORT},http://192.168.64.4:${DEJAVU_PORT},http://dejavu:${DEJAVU_PORT},http://host.docker.internal:${DEJAVU_PORT}'), - new EnvironmentVariable(new Variable('http.cors.enabled'), 'true'), - new EnvironmentVariable(new Variable('http.cors.allow-headers'), 'X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization'), - new EnvironmentVariable(new Variable('http.cors.allow-credentials'), 'true'), + new EnvironmentVariable(new Variable('http.cors.allow-origin'), new Expression('http://', new Variable('APPLICATION_DOMAIN'), ':', new Variable('DEJAVU_PORT'), ',http://')), ) ->addPorts( new PortMapping(new Variable('ELASTICSEARCH_PORT'), 9200) @@ -75,15 +69,24 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack $stack->addFiles( new Resource\InMemory('.docker/elasticsearch/elasticsearch.yml', <<addEnvironmentVariables( new EnvironmentVariable(new Variable('ELASTICSEARCH_PORT')), new EnvironmentVariable(new Variable('DEJAVU_PORT')), + new EnvironmentVariable(new Variable('APPLICATION_DOMAIN')), ); return $stack; diff --git a/src/Domain/Stack/OroPlatform/Service/MySQL.php b/src/Domain/Stack/OroPlatform/Service/MySQL.php index a30ad35..2f5431c 100644 --- a/src/Domain/Stack/OroPlatform/Service/MySQL.php +++ b/src/Domain/Stack/OroPlatform/Service/MySQL.php @@ -3,12 +3,14 @@ namespace Kiboko\Cloud\Domain\Stack\OroPlatform\Service; use Kiboko\Cloud\Domain\Stack\Compose\EnvironmentVariable; +use Kiboko\Cloud\Domain\Stack\Compose\Label; use Kiboko\Cloud\Domain\Stack\Compose\PortMapping; use Kiboko\Cloud\Domain\Stack\Compose\Service; use Kiboko\Cloud\Domain\Stack\Compose\Variable; use Kiboko\Cloud\Domain\Stack\Compose\Volume; use Kiboko\Cloud\Domain\Stack\Compose\VolumeMapping; use Kiboko\Cloud\Domain\Stack\DTO; +use Kiboko\Cloud\Domain\Stack\Resource; use Kiboko\Cloud\Domain\Stack\ServiceBuilderInterface; final class MySQL implements ServiceBuilderInterface @@ -33,15 +35,16 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack new PortMapping(new Variable('SQL_PORT'), 3306), ) ->addEnvironmentVariables( - new EnvironmentVariable(new Variable('MYSQL_ROOT_PASSWORD'), 'password'), - new EnvironmentVariable(new Variable('MYSQL_DATABASE'), 'kiboko'), - new EnvironmentVariable(new Variable('MYSQL_USER'), 'kiboko'), - new EnvironmentVariable(new Variable('MYSQL_PASSWORD'), 'password'), + new EnvironmentVariable(new Variable('MYSQL_ROOT_PASSWORD'), new Variable('SQL_ROOT_PASSWORD')), + new EnvironmentVariable(new Variable('MYSQL_DATABASE'), new Variable('SQL_DATABASE')), + new EnvironmentVariable(new Variable('MYSQL_USER'), new Variable('SQL_USER')), + new EnvironmentVariable(new Variable('MYSQL_PASSWORD'), new Variable('SQL_PASSWORD')), ) ->addVolumeMappings( new VolumeMapping('database', '/var/lib/mysql'), ) ->setRestartOnFailure() + ->setHealthCheckShellCommand('mysqladmin ping --silent -u$${MYSQL_USER} -p$${MYSQL_PASSWORD}') ) ->addVolumes( new Volume('database', ['driver' => 'local']) @@ -49,6 +52,10 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack ; $stack->addEnvironmentVariables( + new EnvironmentVariable(new Variable('SQL_ROOT_PASSWORD')), + new EnvironmentVariable(new Variable('SQL_DATABASE')), + new EnvironmentVariable(new Variable('SQL_USER')), + new EnvironmentVariable(new Variable('SQL_PASSWORD')), new EnvironmentVariable(new Variable('SQL_PORT')), ); diff --git a/src/Domain/Stack/OroPlatform/Service/Nginx.php b/src/Domain/Stack/OroPlatform/Service/Nginx.php index b4fba37..be49efc 100644 --- a/src/Domain/Stack/OroPlatform/Service/Nginx.php +++ b/src/Domain/Stack/OroPlatform/Service/Nginx.php @@ -33,10 +33,12 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack } $stack->addServices( - (new Service('http', 'nginx:alpine')) + ($service = new Service('http', 'nginx:alpine')) ->addVolumeMappings( new VolumeMapping('./.docker/nginx@1.15/config/options.conf', '/etc/nginx/conf.d/000-options.conf'), - new VolumeMapping('./.docker/nginx@1.15/config/reverse-proxy.conf', '/etc/nginx/conf.d/default.conf'), + new VolumeMapping('./.docker/nginx@1.15/config/vhosts/reverse-proxy.conf', '/etc/nginx/conf.d/200-default.conf'), + new VolumeMapping('./.docker/nginx@1.15/config/vhosts/prod.conf', '/etc/nginx/conf.d/100-vhost-prod.conf'), + new VolumeMapping('./.docker/nginx@1.15/config/vhosts/dev.conf', '/etc/nginx/conf.d/100-vhost-dev.conf'), new VolumeMapping('./', '/var/www/html'), new VolumeMapping('cache', '/var/www/html/var/cache', true), new VolumeMapping('assets', '/var/www/html/public/bundles', true), @@ -45,34 +47,8 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack new PortMapping(new Variable('HTTP_PORT'), 80), ) ->setRestartOnFailure() - ->addDependencies(...$servicesDependency), - (new Service('http-worker-prod', 'nginx:alpine')) - ->addVolumeMappings( - new VolumeMapping('./.docker/nginx@1.15/config/options.conf', '/etc/nginx/conf.d/000-options.conf'), - new VolumeMapping('./.docker/nginx@1.15/config/vhost-prod.conf', '/etc/nginx/conf.d/default.conf'), - new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache', true), - new VolumeMapping('assets', '/var/www/html/public/bundles', true), - ) - ->addPorts( - new PortMapping(new Variable('HTTP_PROD_PORT'), 80), - ) - ->setRestartOnFailure() - ->addDependencies('fpm'), - (new Service('http-worker-dev', 'nginx:alpine')) - ->addVolumeMappings( - new VolumeMapping('./.docker/nginx@1.15/config/options.conf', '/etc/nginx/conf.d/000-options.conf'), - new VolumeMapping('./.docker/nginx@1.15/config/vhost-dev.conf', '/etc/nginx/conf.d/default.conf'), - new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache', true), - new VolumeMapping('assets', '/var/www/html/public/bundles', true), - ) - ->addPorts( - new PortMapping(new Variable('HTTP_DEV_PORT'), 80), - ) - ->setRestartOnFailure() - ->addDependencies('fpm') - ); + ->addDependencies(...$servicesDependency) + ); $stack->addFiles( new Resource\InMemory('.docker/nginx@1.15/config/options.conf', <<addEnvironmentVariables( new EnvironmentVariable(new Variable('HTTP_PORT')), - new EnvironmentVariable(new Variable('HTTP_PROD_PORT')), - new EnvironmentVariable(new Variable('HTTP_DEV_PORT')), + new EnvironmentVariable(new Variable('APPLICATION_DOMAIN')), + new EnvironmentVariable(new Variable('DEFAULT_APPLICATION'), 'dev'), ); if ($context->withXdebug) { - $stack->addServices( - (new Service('http-worker-xdebug', 'nginx:alpine')) - ->addVolumeMappings( - new VolumeMapping('./.docker/nginx@1.15/config/options.conf', '/etc/nginx/conf.d/000-options.conf'), - new VolumeMapping('./.docker/nginx@1.15/config/vhost-xdebug.conf', '/etc/nginx/conf.d/default.conf'), - new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache', true), - new VolumeMapping('assets', '/var/www/html/public/bundles', true), - ) - ->addPorts( - new PortMapping(new Variable('HTTP_XDEBUG_PORT'), 80), - ) - ->setRestartOnFailure() - ->addDependencies('fpm-xdebug'), - ); - $stack->addEnvironmentVariables( - new EnvironmentVariable(new Variable('HTTP_XDEBUG_PORT')), + $service->addVolumeMappings( + new VolumeMapping('./.docker/nginx@1.15/config/vhosts/xdebug.conf', '/etc/nginx/conf.d/100-vhost-xdebug.conf'), ); $stack->addFiles( - new Resource\InMemory('.docker/nginx@1.15/config/vhost-xdebug.conf', << 'size=2048m,uid=1000,gid=1000', ], ]), - new Volume('assets', [ - 'driver' => 'local', - 'driver_opts' => [ - 'type' => 'tmpfs', - 'device' => 'tmpfs', - 'o' => 'size=2048m,uid=1000,gid=1000', - ], - ]), - new Volume('cache', [ - 'driver' => 'local', - 'driver_opts' => [ - 'type' => 'tmpfs', - 'device' => 'tmpfs', - 'o' => 'size=2048m,uid=1000,gid=1000', - ], - ]), ); + if ($context->withDockerForMacOptimizations === true) { + $stack->addVolumes( + new Volume('assets', [ + 'driver' => 'local', + 'driver_opts' => [ + 'type' => 'tmpfs', + 'device' => 'tmpfs', + 'o' => 'size=2048m,uid=1000,gid=1000', + ], + ]), + new Volume('cache', [ + 'driver' => 'local', + 'driver_opts' => [ + 'type' => 'tmpfs', + 'device' => 'tmpfs', + 'o' => 'size=2048m,uid=1000,gid=1000', + ], + ]), + ); + } + $stack->addEnvironmentVariables( new EnvironmentVariable(new Variable('COMPOSER_AUTH'), '{"github-oauth":{"github.com":"0000000000000000000000000000000000000000"},"bitbucket-oauth":{"bitbucket.org":{"consumer-key":"0000000000000000000000000000000000000000","consumer-secret":"0000000000000000000000000000000000000000"}}}'), new EnvironmentVariable(new Variable('COMPOSER_PROCESS_TIMEOUT'), 3000), diff --git a/src/Domain/Stack/OroPlatform/Service/PHP/CLI.php b/src/Domain/Stack/OroPlatform/Service/PHP/CLI.php index 06a985f..e1fff9c 100644 --- a/src/Domain/Stack/OroPlatform/Service/PHP/CLI.php +++ b/src/Domain/Stack/OroPlatform/Service/PHP/CLI.php @@ -61,20 +61,25 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack } $stack->addServices( - (new Service('sh', $this->buildImageTag($context))) + ($service = new Service('sh', $this->buildImageTag($context))) ->setUser('docker', 'docker') ->addEnvironmentVariables(...$environment) ->addVolumeMappings( new VolumeMapping(new DTO\Concatenated(new Variable('HOME'), '/.ssh'), '/opt/docker/.ssh/'), new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache'), - new VolumeMapping('assets', '/var/www/html/public/bundles'), new VolumeMapping('composer', '/opt/docker/.composer/'), ) ->setCommand('sleep', '31536000') ->setRestartOnFailure(), ); + if ($context->withDockerForMacOptimizations === true) { + $service->addVolumeMappings( + new VolumeMapping('cache', '/var/www/html/var/cache'), + new VolumeMapping('assets', '/var/www/html/public/bundles'), + ); + } + return $stack; } } \ No newline at end of file diff --git a/src/Domain/Stack/OroPlatform/Service/PHP/CLIWithXdebug.php b/src/Domain/Stack/OroPlatform/Service/PHP/CLIWithXdebug.php index 52d3aea..f50adff 100644 --- a/src/Domain/Stack/OroPlatform/Service/PHP/CLIWithXdebug.php +++ b/src/Domain/Stack/OroPlatform/Service/PHP/CLIWithXdebug.php @@ -43,20 +43,24 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack ]; $stack->addServices( - (new Service('sh-xdebug', $this->buildImageTag($context))) + ($service = new Service('sh-xdebug', $this->buildImageTag($context))) ->setUser('docker', 'docker') ->addEnvironmentVariables(...$environment) ->addVolumeMappings( new VolumeMapping(new DTO\Concatenated(new Variable('HOME'), '/.ssh'), '/opt/docker/.ssh/'), new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache'), - new VolumeMapping('assets', '/var/www/html/public/bundles'), new VolumeMapping('composer', '/opt/docker/.composer/'), ) ->setCommand('sleep', '31536000') ->setRestartOnFailure(), - ) - ; + ); + + if ($context->withDockerForMacOptimizations === true) { + $service->addVolumeMappings( + new VolumeMapping('cache', '/var/www/html/var/cache'), + new VolumeMapping('assets', '/var/www/html/public/bundles'), + ); + } return $stack; } diff --git a/src/Domain/Stack/OroPlatform/Service/PHP/DBGP.php b/src/Domain/Stack/OroPlatform/Service/PHP/DBGP.php index 36f6e6d..15f2f62 100644 --- a/src/Domain/Stack/OroPlatform/Service/PHP/DBGP.php +++ b/src/Domain/Stack/OroPlatform/Service/PHP/DBGP.php @@ -8,6 +8,7 @@ use Kiboko\Cloud\Domain\Stack\Compose\Variable; use Kiboko\Cloud\Domain\Stack\DTO; use Kiboko\Cloud\Domain\Stack\DTO\Context; +use Kiboko\Cloud\Domain\Stack\Resource; use Kiboko\Cloud\Domain\Stack\ServiceBuilderInterface; final class DBGP implements ServiceBuilderInterface @@ -43,9 +44,9 @@ public function build(DTO\Stack $stack, Context $context): DTO\Stack RUN set -ex \ && apk add \ - py-pip \ - && pip2 install --upgrade pip \ - && pip2 install komodo-python-dbgp + py3-pip \ + && pip install --upgrade pip \ + && pip install komodo-python3-dbgp CMD /usr/bin/pydbgpproxy -d 0.0.0.0:9000 -i 0.0.0.0:9001 diff --git a/src/Domain/Stack/OroPlatform/Service/PHP/FPM.php b/src/Domain/Stack/OroPlatform/Service/PHP/FPM.php index 894ae5d..04b5e9d 100644 --- a/src/Domain/Stack/OroPlatform/Service/PHP/FPM.php +++ b/src/Domain/Stack/OroPlatform/Service/PHP/FPM.php @@ -61,17 +61,22 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack } $stack->addServices( - (new Service('fpm', $this->buildImageTag($context))) + ($service = new Service('fpm', $this->buildImageTag($context))) ->setUser('docker', 'docker') ->addEnvironmentVariables(...$environment) ->addVolumeMappings( new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache'), - new VolumeMapping('assets', '/var/www/html/public/bundles'), ) ->setRestartOnFailure(), ); + if ($context->withDockerForMacOptimizations === true) { + $service->addVolumeMappings( + new VolumeMapping('cache', '/var/www/html/var/cache'), + new VolumeMapping('assets', '/var/www/html/public/bundles'), + ); + } + return $stack; } } \ No newline at end of file diff --git a/src/Domain/Stack/OroPlatform/Service/PHP/FPMWithXdebug.php b/src/Domain/Stack/OroPlatform/Service/PHP/FPMWithXdebug.php index f7029fe..321e02e 100644 --- a/src/Domain/Stack/OroPlatform/Service/PHP/FPMWithXdebug.php +++ b/src/Domain/Stack/OroPlatform/Service/PHP/FPMWithXdebug.php @@ -42,17 +42,22 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack ]; $stack->addServices( - (new Service('fpm-xdebug', $this->buildImageTag($context))) + ($service = new Service('fpm-xdebug', $this->buildImageTag($context))) ->setUser('docker', 'docker') ->addEnvironmentVariables(...$environment) ->addVolumeMappings( new VolumeMapping('./', '/var/www/html'), - new VolumeMapping('cache', '/var/www/html/var/cache'), - new VolumeMapping('assets', '/var/www/html/public/bundles'), ) ->setRestartOnFailure(), ); + if ($context->withDockerForMacOptimizations === true) { + $service->addVolumeMappings( + new VolumeMapping('cache', '/var/www/html/var/cache'), + new VolumeMapping('assets', '/var/www/html/public/bundles'), + ); + } + return $stack; } } \ No newline at end of file diff --git a/src/Domain/Stack/OroPlatform/Service/PostgreSQL.php b/src/Domain/Stack/OroPlatform/Service/PostgreSQL.php index 6bf8ffb..4737d69 100644 --- a/src/Domain/Stack/OroPlatform/Service/PostgreSQL.php +++ b/src/Domain/Stack/OroPlatform/Service/PostgreSQL.php @@ -34,16 +34,17 @@ public function build(DTO\Stack $stack, DTO\Context $context): DTO\Stack new PortMapping(new Variable('SQL_PORT'), 5432), ) ->addEnvironmentVariables( - new EnvironmentVariable(new Variable('POSTGRES_ROOT_PASSWORD'), 'password'), - new EnvironmentVariable(new Variable('POSTGRES_DB'), 'kiboko'), - new EnvironmentVariable(new Variable('POSTGRES_USER'), 'kiboko'), - new EnvironmentVariable(new Variable('POSTGRES_PASSWORD'), 'password'), + new EnvironmentVariable(new Variable('POSTGRES_ROOT_PASSWORD'), new Variable('SQL_ROOT_PASSWORD')), + new EnvironmentVariable(new Variable('POSTGRES_DB'), new Variable('SQL_DATABASE')), + new EnvironmentVariable(new Variable('POSTGRES_USER'), new Variable('SQL_USER')), + new EnvironmentVariable(new Variable('POSTGRES_PASSWORD'), new Variable('SQL_PASSWORD')), ) ->addVolumeMappings( new VolumeMapping('./.docker/postgres@9.6/sql/uuid-ossp.sql', '/docker-entrypoint-initdb.d/00-uuid-ossp.sql', true), new VolumeMapping('database', '/var/lib/postgresql/data'), ) ->setRestartOnFailure() + ->setHealthCheckShellCommand('pg_isready -U$${POSTGRES_USER} -D$${POSTGRES_DB}') ) ->addVolumes( new Volume('database', ['driver' => 'local']) diff --git a/src/Platform/Console/Command/Stack/InitCommand.php b/src/Platform/Console/Command/Stack/InitCommand.php index bdcf684..f1f59d6 100644 --- a/src/Platform/Console/Command/Stack/InitCommand.php +++ b/src/Platform/Console/Command/Stack/InitCommand.php @@ -7,9 +7,12 @@ use Kiboko\Cloud\Platform\Console\ContextWizard; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Serializer; final class InitCommand extends Command { @@ -38,14 +41,38 @@ protected function execute(InputInterface $input, OutputInterface $output) { $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + $format = new SymfonyStyle($input, $output); - if (file_exists($workingDirectory . '/.kloud.yml')) { - $format->error('The directory was already initialized with a Docker stack.'); - return 0; + $serializer = new Serializer( + [ + new PropertyNormalizer(), + ], + [ + new YamlEncoder() + ] + ); + + if ($finder->hasResults()) { + /** @var \SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.ya?ml$/') as $file) { + $format->error('The directory was already initialized with a Docker stack. You should instead run stack:upgrade command.'); + return 0; + } } - $context = ($this->wizard)($input, $output, $workingDirectory); + $context = ($this->wizard)($input, $output); + + $format->note('Writing a new .kloud.yaml file.'); + file_put_contents($workingDirectory . '/.kloud.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 2, + 'yaml_indent' => 2, + 'yaml_flags' => 0 + ])); $builder = new StackBuilder( new OroPlatform\Builder($this->stacksPath), diff --git a/src/Platform/Console/Command/Stack/UpgradeCommand.php b/src/Platform/Console/Command/Stack/UpgradeCommand.php index 9b8a04b..559ab72 100644 --- a/src/Platform/Console/Command/Stack/UpgradeCommand.php +++ b/src/Platform/Console/Command/Stack/UpgradeCommand.php @@ -8,13 +8,14 @@ use Kiboko\Cloud\Platform\Console\ServicePrinter; use Kiboko\Cloud\Platform\Console\ContextWizard; use Kiboko\Cloud\Platform\Console\VolumePrinter; +use SebastianBergmann\Diff\Differ; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Serializer\Encoder\YamlEncoder; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; @@ -63,7 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ); if ($finder->hasResults()) { - /** @var \SplFileInfo $file */ + /** @var SplFileInfo $file */ foreach ($finder->name('/^\.?kloud.ya?ml$/') as $file) { try { /** @var Context $context */ @@ -76,11 +77,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - if (empty($context)) { - $context = ($this->wizard)($input, $output, $workingDirectory); + if (!isset($context)) { + $context = ($this->wizard)($input, $output); $format->note('Writing a new .kloud.yaml file.'); - file_put_contents($workingDirectory . '/.kloud.yaml', $serializer->serialize($context, 'yaml')); + file_put_contents($workingDirectory . '/.kloud.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 2, + 'yaml_indent' => 2, + 'yaml_flags' => 0 + ])); } $builder = new StackBuilder( @@ -108,6 +113,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $format->note(strtr('Your stack is having %count% non-standard services.', ['%count%' => $extraServicesCount])); foreach ($extraServices as $service) { + if (in_array($service->getName(), $context->selfManagedServices)) { + $format->note(strtr('Ignoring service %serviceName% as it is marked as self-managed.', ['%serviceName%' => $service->getName()])); + continue; + } $printer->printService($service, $input, $output); if (!$format->askQuestion(new ConfirmationQuestion('Do you wish to keep this service in your stack?'))) { @@ -119,13 +128,29 @@ protected function execute(InputInterface $input, OutputInterface $output) $updatedServicesCount = 0; if (count($commonServices = $diff->diffServices()) > 0) { foreach ($commonServices as $serviceDiff) { - if (!$printer->printServiceDiff($serviceDiff, $input, $output)) { + $diffChunks = $serviceDiff->diff(); + + $differences = array_filter($diffChunks->getChunks(), function (array $chunk) { + return $chunk[1] === Differ::ADDED || $chunk[1] === Differ::REMOVED; + }); + + if (count($differences) <= 0) { continue; } + if (in_array($serviceDiff->getName(), $context->selfManagedServices)) { + $format->note(strtr('Ignoring service %serviceName% as it is marked as self-managed.', ['%serviceName%' => $serviceDiff->getName()])); + $stack->replaceServices($serviceDiff->getFrom()); + continue; + } + + $format->title(strtr('Service %service%', ['%service%' => $serviceDiff->getName()])); + + $printer->printServiceDiff($diffChunks, $input, $output); + ++$updatedServicesCount; if (!$format->askQuestion(new ConfirmationQuestion('Do you wish to update this service in your stack?'))) { - $stack->replaceServices($serviceDiff->getTo()); + $stack->replaceServices($serviceDiff->getFrom()); } } } @@ -147,6 +172,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $format->note(strtr('Your stack is having %count% non-standard volumes.', ['%count%' => $extraVolumesCount])); foreach ($extraVolumes as $volume) { + if (in_array($volume->getName(), $context->selfManagedVolumes)) { + $format->note(strtr('Ignoring volume %volumeName% as it is marked as self-managed.', ['%volumeName%' => $volume->getName()])); + continue; + } $printer->printVolume($volume, $input, $output); if (!$format->askQuestion(new ConfirmationQuestion('Do you wish to keep this volume in your stack?'))) { @@ -158,13 +187,29 @@ protected function execute(InputInterface $input, OutputInterface $output) $updatedVolumesCount = 0; if (count($commonVolumes = $diff->diffVolumes()) > 0) { foreach ($commonVolumes as $volumeDiff) { - if (!$printer->printVolumeDiff($volumeDiff, $input, $output)) { + $diffChunks = $volumeDiff->diff(); + + $differences = array_filter($diffChunks->getChunks(), function (array $chunk) { + return $chunk[1] === Differ::ADDED || $chunk[1] === Differ::REMOVED; + }); + + if (count($differences) <= 0) { continue; } + if (in_array($volumeDiff->getName(), $context->selfManagedVolumes)) { + $format->note(strtr('Ignoring volume %volumeName% as it is marked as self-managed.', ['%volumeName%' => $volumeDiff->getName()])); + $stack->replaceVolumes($volumeDiff->getFrom()); + continue; + } + + $format->title(strtr('Volume %volume%', ['%volume%' => $volumeDiff->getName()])); + + $printer->printVolumeDiff($diffChunks, $input, $output); + ++$updatedVolumesCount; if (!$format->askQuestion(new ConfirmationQuestion('Do you wish to update this volume in your stack?'))) { - $stack->replaceServices($serviceDiff->getTo()); + $stack->replaceVolumes($volumeDiff->getFrom()); } } } diff --git a/src/Platform/Console/ContextWizard.php b/src/Platform/Console/ContextWizard.php index 93fda17..95e406c 100644 --- a/src/Platform/Console/ContextWizard.php +++ b/src/Platform/Console/ContextWizard.php @@ -56,6 +56,12 @@ public function configureConsoleCommand(Command $command) $command->addOption('without-xdebug', null, InputOption::VALUE_NONE, 'Set up the application without Xdebug.'); $command->addOption('with-blackfire', null, InputOption::VALUE_NONE, 'Set up the application to use Blackfire.'); $command->addOption('without-blackfire', null, InputOption::VALUE_NONE, 'Set up the application without Blackfire.'); + $command->addOption('with-dejavu', null, InputOption::VALUE_NONE, 'Set up the application to use Dejavu UI.'); + $command->addOption('without-dejavu', null, InputOption::VALUE_NONE, 'Set up the application without Dejavu UI.'); + $command->addOption('with-elastic-stack', null, InputOption::VALUE_NONE, 'Set up the application to use Elastic Stack logging.'); + $command->addOption('without-elastic-stack', null, InputOption::VALUE_NONE, 'Set up the application without Elastic Stack logging.'); + $command->addOption('with-macos-optimizations', null, InputOption::VALUE_NONE, 'Set up the application to use Docker for Mac optimizations.'); + $command->addOption('without-macos-optimizations', null, InputOption::VALUE_NONE, 'Set up the application without Docker for Mac optimizations.'); } public function __invoke(InputInterface $input, OutputInterface $output): Stack\DTO\Context @@ -98,9 +104,36 @@ public function __invoke(InputInterface $input, OutputInterface $output): Stack\ (new ConfirmationQuestion('Include XDebug?', $context->withXdebug ?? false)) ); } + if ($input->getOption('with-dejavu')) { + $context->withDejavu = true; + } else if ($input->getOption('without-dejavu')) { + $context->withDejavu = false; + } else { + $context->withDejavu = $format->askQuestion( + (new ConfirmationQuestion('Include Dejavu UI?', $context->withDejavu ?? false)) + ); + } + if ($input->getOption('with-elastic-stack')) { + $context->withElasticStack = true; + } else if ($input->getOption('without-elastic-stack')) { + $context->withElasticStack = false; + } else { + $context->withElasticStack = $format->askQuestion( + (new ConfirmationQuestion('Include Elastic Stack logging?', $context->withElasticStack ?? false)) + ); + } + if ($input->getOption('with-macos-optimizations')) { + $context->withDockerForMacOptimizations = true; + } else if ($input->getOption('without-macos-optimizations')) { + $context->withDockerForMacOptimizations = false; + } else { + $context->withDockerForMacOptimizations = $format->askQuestion( + (new ConfirmationQuestion('Activate Docker for Mac optimizations?', $context->withDockerForMacOptimizations ?? false)) + ); + } $format->table( - ['php', 'application', 'edition', 'version', 'database', 'blackfire', 'xdebug'], + ['php', 'application', 'edition', 'version', 'database', 'blackfire', 'xdebug', 'dejavu', 'elastic', 'macos'], [[ $context->phpVersion, $context->application ?: '', @@ -109,6 +142,9 @@ public function __invoke(InputInterface $input, OutputInterface $output): Stack\ $context->dbms ?: '', $context->withBlackfire ? 'yes' : 'no', $context->withXdebug ? 'yes' : 'no', + $context->withDejavu ? 'yes' : 'no', + $context->withElasticStack ? 'yes' : 'no', + $context->withDockerForMacOptimizations ? 'yes' : 'no', ]] ); diff --git a/src/Platform/Console/ServicePrinter.php b/src/Platform/Console/ServicePrinter.php index 3918cd4..6b2dd60 100644 --- a/src/Platform/Console/ServicePrinter.php +++ b/src/Platform/Console/ServicePrinter.php @@ -7,9 +7,8 @@ use Kiboko\Cloud\Domain\Stack\Compose\Service; use Kiboko\Cloud\Domain\Stack\Compose\ValuedEnvironmentVariableInterface; use Kiboko\Cloud\Domain\Stack\Compose\VolumeMapping; -use Kiboko\Cloud\Domain\Stack\Diff\ServiceDiff; use Kiboko\Cloud\Domain\Stack\Diff\UnifiedDiffOutputBuilder; -use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Diff; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -61,23 +60,10 @@ public function printService(Service $service, InputInterface $input, OutputInte } } - public function printServiceDiff(ServiceDiff $serviceDiff, InputInterface $input, OutputInterface $output): bool + public function printServiceDiff(Diff $diff, InputInterface $input, OutputInterface $output): void { - $diff = $serviceDiff->diff(); - $differences = array_filter($diff->getChunks(), function (array $chunk) { - return $chunk[1] === Differ::ADDED || $chunk[1] === Differ::REMOVED; - }); - - if (count($differences) <= 0) { - return false; - } - $format = new SymfonyStyle($input, $output); - $format->title(strtr('Service %service%', ['%service%' => $serviceDiff->getName()])); - $format->write((string) (new UnifiedDiffOutputBuilder())->getDiff($diff->getChunks())); - - return true; } } \ No newline at end of file diff --git a/src/Platform/Console/VolumePrinter.php b/src/Platform/Console/VolumePrinter.php index 403953c..cf4f330 100644 --- a/src/Platform/Console/VolumePrinter.php +++ b/src/Platform/Console/VolumePrinter.php @@ -5,7 +5,7 @@ use Kiboko\Cloud\Domain\Stack\Compose\Volume; use Kiboko\Cloud\Domain\Stack\Diff\VolumeDiff; use Kiboko\Cloud\Domain\Stack\Diff\UnifiedDiffOutputBuilder; -use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Diff; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -26,23 +26,10 @@ public function printVolume(Volume $volume, InputInterface $input, OutputInterfa ); } - public function printVolumeDiff(VolumeDiff $volumeDiff, InputInterface $input, OutputInterface $output): bool + public function printVolumeDiff(Diff $diff, InputInterface $input, OutputInterface $output): void { - $diff = $volumeDiff->diff(); - $differences = array_filter($diff->getChunks(), function (array $chunk) { - return $chunk[1] === Differ::ADDED || $chunk[1] === Differ::REMOVED; - }); - - if (count($differences) <= 0) { - return false; - } - $format = new SymfonyStyle($input, $output); - $format->title(strtr('Volume %volume%', ['%volume%' => $volumeDiff->getName()])); - $format->write((string) (new UnifiedDiffOutputBuilder())->getDiff($diff->getChunks())); - - return true; } } \ No newline at end of file diff --git a/src/Platform/Context/ComposerPackageGuesser/MarelloCommunity.php b/src/Platform/Context/ComposerPackageGuesser/MarelloCommunity.php index 5532001..c8f5f3f 100644 --- a/src/Platform/Context/ComposerPackageGuesser/MarelloCommunity.php +++ b/src/Platform/Context/ComposerPackageGuesser/MarelloCommunity.php @@ -23,19 +23,31 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^2.0')) { - return new Stack\DTO\Context('7.2', 'marello', '2.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.0', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.1')) { - return new Stack\DTO\Context('7.2', 'marello', '2.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.2')) { - return new Stack\DTO\Context('7.2', 'marello', '2.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.2', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^3.0')) { - return new Stack\DTO\Context('7.4', 'marello', '3.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '3.0', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/MarelloEnterprise.php b/src/Platform/Context/ComposerPackageGuesser/MarelloEnterprise.php index a424a63..6d56c28 100644 --- a/src/Platform/Context/ComposerPackageGuesser/MarelloEnterprise.php +++ b/src/Platform/Context/ComposerPackageGuesser/MarelloEnterprise.php @@ -23,19 +23,31 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^2.0')) { - return new Stack\DTO\Context('7.2', 'marello', '2.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.0', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.1')) { - return new Stack\DTO\Context('7.2', 'marello', '2.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.2')) { - return new Stack\DTO\Context('7.2', 'marello', '2.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '2.2', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^3.0')) { - return new Stack\DTO\Context('7.4', 'marello', '3.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('marello', '3.0', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/MiddlewareCommunity.php b/src/Platform/Context/ComposerPackageGuesser/MiddlewareCommunity.php index d66b423..1eb0ad1 100644 --- a/src/Platform/Context/ComposerPackageGuesser/MiddlewareCommunity.php +++ b/src/Platform/Context/ComposerPackageGuesser/MiddlewareCommunity.php @@ -23,7 +23,10 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^1.0')) { - return new Stack\DTO\Context('7.4', 'middleware', '1.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('middleware', '1.0', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/MiddlewareEnterprise.php b/src/Platform/Context/ComposerPackageGuesser/MiddlewareEnterprise.php index 19c9c50..6e7e637 100644 --- a/src/Platform/Context/ComposerPackageGuesser/MiddlewareEnterprise.php +++ b/src/Platform/Context/ComposerPackageGuesser/MiddlewareEnterprise.php @@ -23,7 +23,10 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^1.0')) { - return new Stack\DTO\Context('7.4', 'middleware', '1.0', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('middleware', '1.0', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroCRMCommunity.php b/src/Platform/Context/ComposerPackageGuesser/OroCRMCommunity.php index fbe427f..74f87b3 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroCRMCommunity.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroCRMCommunity.php @@ -23,15 +23,24 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'orocrm', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '3.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'orocrm', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '4.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'orocrm', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '4.2', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroCRMEnterprise.php b/src/Platform/Context/ComposerPackageGuesser/OroCRMEnterprise.php index 5624b94..c89ba2f 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroCRMEnterprise.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroCRMEnterprise.php @@ -23,15 +23,24 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'orocrm', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '3.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'orocrm', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '4.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'orocrm', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocrm', '4.2', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroCommerceCommunity.php b/src/Platform/Context/ComposerPackageGuesser/OroCommerceCommunity.php index e4aaa3f..af95afa 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroCommerceCommunity.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroCommerceCommunity.php @@ -23,15 +23,24 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'orocommerce', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '3.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'orocommerce', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '4.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'orocommerce', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '4.2', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroCommerceEnterprise.php b/src/Platform/Context/ComposerPackageGuesser/OroCommerceEnterprise.php index 9527511..179e4dd 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroCommerceEnterprise.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroCommerceEnterprise.php @@ -23,15 +23,24 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'orocommerce', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '3.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'orocommerce', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '4.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'orocommerce', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('orocommerce', '4.2', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroPlatformCommunity.php b/src/Platform/Context/ComposerPackageGuesser/OroPlatformCommunity.php index c04acb5..7f72625 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroPlatformCommunity.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroPlatformCommunity.php @@ -23,23 +23,38 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^1.8')) { - return new Stack\DTO\Context('5.6', 'oroplatform', '1.8', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('5.6')) + ->setApplication('oroplatform', '1.8', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.6')) { - return new Stack\DTO\Context('5.6', 'oroplatform', '2.6', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('5.6')) + ->setApplication('oroplatform', '2.6', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'oroplatform', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '3.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'oroplatform', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '4.1', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'oroplatform', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, false); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '4.2', false) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); diff --git a/src/Platform/Context/ComposerPackageGuesser/OroPlatformEnterprise.php b/src/Platform/Context/ComposerPackageGuesser/OroPlatformEnterprise.php index 300a9c1..11f45d9 100644 --- a/src/Platform/Context/ComposerPackageGuesser/OroPlatformEnterprise.php +++ b/src/Platform/Context/ComposerPackageGuesser/OroPlatformEnterprise.php @@ -23,23 +23,38 @@ public function matches(array $package): bool public function guess(array $package): Stack\DTO\Context { if (Semver::satisfies($package['version'], '^1.8')) { - return new Stack\DTO\Context('5.6', 'oroplatform', '1.8', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('5.6')) + ->setApplication('oroplatform', '1.8', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^2.6')) { - return new Stack\DTO\Context('5.6', 'oroplatform', '2.6', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('5.6')) + ->setApplication('oroplatform', '2.6', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^3.1')) { - return new Stack\DTO\Context('7.2', 'oroplatform', '3.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '3.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.1')) { - return new Stack\DTO\Context('7.4', 'oroplatform', '4.1', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '4.1', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } if (Semver::satisfies($package['version'], '^4.2')) { - return new Stack\DTO\Context('7.4', 'oroplatform', '4.2', Stack\DTO\Context::DBMS_POSTGRESQL, true, true, true); + return (new Stack\ContextBuilder('7.2')) + ->setApplication('oroplatform', '4.2', true) + ->setDbms(Stack\DTO\Context::DBMS_POSTGRESQL) + ->getContext(); } throw NoPossibleGuess::noVersionMatching(); From a075f5cb0d07df669b677a62f3282d88ffa5a76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylvain=20Ray=C3=A9?= <1337461+sylvainraye@users.noreply.github.com> Date: Mon, 21 Sep 2020 14:22:50 +0200 Subject: [PATCH 04/23] update doc to know in advance that php 7.4 is required if someone is not using install via composer --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4a0f4aa..55c4104 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc > ⚠️ Nota: Those stacks are not suited for production hosting, but to provide an environment based on Docker as identical as possible to OroCloud on a personal computer. +* [Requirements](#requirements) * [Installation](#installation) * [Usage](#usage) * [Supported versions and flavors](#supported-versions-and-flavours) @@ -14,6 +15,11 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc * [Marello](#marello) * [Middleware](#middleware) +Requirements: +--- + +- PHP 7.4 + Installation --- From 762af9760ef69380712e2369e497f9006580d421 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Wed, 23 Sep 2020 15:04:58 +0200 Subject: [PATCH 05/23] #12 : add environment:init --- bin/kloud | 4 +- .../Command/Environment/InitCommand.php | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/Platform/Console/Command/Environment/InitCommand.php diff --git a/bin/kloud b/bin/kloud index e5d5e24..3247b71 100755 --- a/bin/kloud +++ b/bin/kloud @@ -59,6 +59,8 @@ $app->addCommands([ __DIR__ . '/../config/', __DIR__ . '/../compose/', ))->setAliases(['upgrade']), + + (new Command\Environment\InitCommand())->setAliases(['env:init']), ]); -$app->run(new ArgvInput($argv), new ConsoleOutput()); \ No newline at end of file +$app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/InitCommand.php b/src/Platform/Console/Command/Environment/InitCommand.php new file mode 100644 index 0000000..4a28f65 --- /dev/null +++ b/src/Platform/Console/Command/Environment/InitCommand.php @@ -0,0 +1,52 @@ +setDescription('Initialize the environment file in local workspace'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $format = new SymfonyStyle($input, $output); + $allLines = []; + + $serverAddress = $format->askQuestion(new Question('Server of your remote directory')); + $newLine = 'SERVER_ADDRESS' . ': ' . $serverAddress . PHP_EOL; + array_push($allLines, $newLine); + + $depPath = $format->askQuestion(new Question('Path of your remote directory on the server')); + $newLine = 'DEPLOYMENT_PATH' . ': ' . $depPath . PHP_EOL; + array_push($allLines, $newLine); + + $envDistPath = getcwd() . '/.env.dist'; + if (file_exists($envDistPath)) { + $envDist = parse_ini_file($envDistPath); + foreach (array_keys($envDist) as $line) { + $lineValue = $format->askQuestion(new Question('Value of ' . $line)); + $newLine = $line . ': ' . $lineValue . PHP_EOL; + array_push($allLines, $newLine); + } + } + + $newFile = fopen('.kloud.environent.yaml', 'w'); + foreach ($allLines as $line) { + fwrite($newFile, $line); + } + + return 0; + } +} From 3bb55d98aabf08dc7ad271315f1da82125be04c6 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Thu, 24 Sep 2020 12:22:16 +0200 Subject: [PATCH 06/23] #12 : refacto --- bin/kloud | 2 +- .../Console/Command/Environment/InitCommand.php | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bin/kloud b/bin/kloud index 3247b71..b771c43 100755 --- a/bin/kloud +++ b/bin/kloud @@ -60,7 +60,7 @@ $app->addCommands([ __DIR__ . '/../compose/', ))->setAliases(['upgrade']), - (new Command\Environment\InitCommand())->setAliases(['env:init']), + (new Command\Environment\InitCommand()), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/InitCommand.php b/src/Platform/Console/Command/Environment/InitCommand.php index 4a28f65..4d356c6 100644 --- a/src/Platform/Console/Command/Environment/InitCommand.php +++ b/src/Platform/Console/Command/Environment/InitCommand.php @@ -9,6 +9,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Yaml; final class InitCommand extends Command { @@ -25,27 +26,22 @@ protected function execute(InputInterface $input, OutputInterface $output) $allLines = []; $serverAddress = $format->askQuestion(new Question('Server of your remote directory')); - $newLine = 'SERVER_ADDRESS' . ': ' . $serverAddress . PHP_EOL; - array_push($allLines, $newLine); + $allLines['SERVER_ADDRESS'] = $serverAddress; $depPath = $format->askQuestion(new Question('Path of your remote directory on the server')); - $newLine = 'DEPLOYMENT_PATH' . ': ' . $depPath . PHP_EOL; - array_push($allLines, $newLine); + $allLines['DEPLOYMENT_PATH'] = $depPath; $envDistPath = getcwd() . '/.env.dist'; if (file_exists($envDistPath)) { $envDist = parse_ini_file($envDistPath); foreach (array_keys($envDist) as $line) { $lineValue = $format->askQuestion(new Question('Value of ' . $line)); - $newLine = $line . ': ' . $lineValue . PHP_EOL; - array_push($allLines, $newLine); + $allLines[$line] = $lineValue; } } - $newFile = fopen('.kloud.environent.yaml', 'w'); - foreach ($allLines as $line) { - fwrite($newFile, $line); - } + $yaml = Yaml::dump($allLines); + file_put_contents('.kloud.environent.yaml', $yaml); return 0; } From 4afd1801b004ec810e75fbc2cf33868d0d8c5264 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Wed, 30 Sep 2020 11:51:09 +0200 Subject: [PATCH 07/23] #12 : big commit adding DTO, some refacto, get and list command --- bin/kloud | 12 ++- src/Domain/Environment/DTO/Context.php | 100 ++++++++++++++++++ src/Domain/Environment/DTO/Deployment.php | 15 +++ .../DTO/DirectValueEnvironmentVariable.php | 26 +++++ .../Environment/DTO/EnvironmentVariable.php | 18 ++++ .../DTO/EnvironmentVariableInterface.php | 8 ++ src/Domain/Environment/DTO/Expression.php | 35 ++++++ .../Environment/DTO/ExpressionParser.php | 32 ++++++ .../DTO/SecretValueEnvironmentVariable.php | 25 +++++ src/Domain/Environment/DTO/Server.php | 17 +++ .../ValuedEnvironmentVariableInterface.php | 9 ++ src/Domain/Environment/DTO/Variable.php | 18 ++++ .../Command/Environment/InitCommand.php | 78 +++++++++++--- .../Environment/Variable/GetCommand.php | 98 +++++++++++++++++ .../Environment/Variable/ListCommand.php | 95 +++++++++++++++++ .../Console/Command/Stack/InitCommand.php | 2 +- src/Platform/Console/EnvironmentWizard.php | 16 +++ 17 files changed, 588 insertions(+), 16 deletions(-) create mode 100644 src/Domain/Environment/DTO/Context.php create mode 100644 src/Domain/Environment/DTO/Deployment.php create mode 100644 src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php create mode 100644 src/Domain/Environment/DTO/EnvironmentVariable.php create mode 100644 src/Domain/Environment/DTO/EnvironmentVariableInterface.php create mode 100644 src/Domain/Environment/DTO/Expression.php create mode 100644 src/Domain/Environment/DTO/ExpressionParser.php create mode 100644 src/Domain/Environment/DTO/SecretValueEnvironmentVariable.php create mode 100644 src/Domain/Environment/DTO/Server.php create mode 100644 src/Domain/Environment/DTO/ValuedEnvironmentVariableInterface.php create mode 100644 src/Domain/Environment/DTO/Variable.php create mode 100644 src/Platform/Console/Command/Environment/Variable/GetCommand.php create mode 100644 src/Platform/Console/Command/Environment/Variable/ListCommand.php create mode 100644 src/Platform/Console/EnvironmentWizard.php diff --git a/bin/kloud b/bin/kloud index b771c43..b47f987 100755 --- a/bin/kloud +++ b/bin/kloud @@ -60,7 +60,17 @@ $app->addCommands([ __DIR__ . '/../compose/', ))->setAliases(['upgrade']), - (new Command\Environment\InitCommand()), + (new Command\Environment\InitCommand( + Command\Environment\InitCommand::$defaultName, + )), + + (new Command\Environment\Variable\ListCommand( + Command\Environment\Variable\ListCommand::$defaultName, + )), + + (new Command\Environment\Variable\GetCommand( + Command\Environment\Variable\GetCommand::$defaultName, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Domain/Environment/DTO/Context.php b/src/Domain/Environment/DTO/Context.php new file mode 100644 index 0000000..75b930b --- /dev/null +++ b/src/Domain/Environment/DTO/Context.php @@ -0,0 +1,100 @@ +deployment = $deployment; + $this->environmentVariables = []; + } + + public function addVariables(EnvironmentVariableInterface ...$variables): void + { + array_push($this->environmentVariables, ...$variables); + } + + public function getVariableValue(string $variableName) + { + foreach ($this->environmentVariables as $variable) { + if ($variable instanceof DirectValueEnvironmentVariable) { + if ($variableName === $variable->getVariable()->__toString()) { + $value = $variable->getValue()->__toString(); + if (!$value) { + $value = ' '; + } + return $value; + } + } else if ($variable instanceof SecretValueEnvironmentVariable) { + if ($variableName === $variable->getVariable()->__toString()) { + return '**secret**'; + } + } else { + if ($variableName === $variable->getVariable()->__toString()) { + return ' '; + } + } + } + } + + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + { + $this->deployment = $denormalizer->denormalize($data['deployment'], Deployment::class, $format, $context); + $this->environmentVariables = []; + + $parser = new ExpressionParser(); + foreach ($data['environmentVariables'] as $variable) { + if (isset($variable['value'])) { + $this->environmentVariables[] = new DirectValueEnvironmentVariable( + new Variable($variable['name']), + $parser->parse($variable['value']) + ); + } else if (isset($variable['secret'])) { + $this->environmentVariables[] = new SecretValueEnvironmentVariable( + new Variable($variable['name']), + $variable['secret'] + ); + } else { + $this->environmentVariables[] = new EnvironmentVariable( + new Variable($variable['name']) + ); + } + } + } + + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + { + return [ + 'deployment' => $normalizer->normalize($this->deployment, $format, $context), + 'environment' => (function($variables) { + /** @var EnvironmentVariableInterface $variable */ + foreach ($variables as $variable) { + if ($variable instanceof DirectValueEnvironmentVariable) { + yield [ + 'name' => (string) $variable->getVariable(), + 'value' => $variable->getValue(), + ]; + } else if ($variable instanceof SecretValueEnvironmentVariable){ + yield [ + 'name' => (string) $variable->getVariable(), + 'secret' => $variable->getSecret(), + ]; + } else { + yield [ + 'name' => (string) $variable->getVariable(), + ]; + } + } + })($this->environmentVariables), + ]; + } +} diff --git a/src/Domain/Environment/DTO/Deployment.php b/src/Domain/Environment/DTO/Deployment.php new file mode 100644 index 0000000..f626ff2 --- /dev/null +++ b/src/Domain/Environment/DTO/Deployment.php @@ -0,0 +1,15 @@ +server = $server; + $this->path = $path; + } +} diff --git a/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php b/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php new file mode 100644 index 0000000..9a29c90 --- /dev/null +++ b/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php @@ -0,0 +1,26 @@ +variable = $variable; + $this->value = $value; + } + + public function getVariable(): Variable + { + return $this->variable; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/src/Domain/Environment/DTO/EnvironmentVariable.php b/src/Domain/Environment/DTO/EnvironmentVariable.php new file mode 100644 index 0000000..323966c --- /dev/null +++ b/src/Domain/Environment/DTO/EnvironmentVariable.php @@ -0,0 +1,18 @@ +variable = $variable; + } + + public function getVariable(): Variable + { + return $this->variable; + } +} diff --git a/src/Domain/Environment/DTO/EnvironmentVariableInterface.php b/src/Domain/Environment/DTO/EnvironmentVariableInterface.php new file mode 100644 index 0000000..9c9ddbc --- /dev/null +++ b/src/Domain/Environment/DTO/EnvironmentVariableInterface.php @@ -0,0 +1,8 @@ +elements = $elements; + } + + public function __toString() + { + return implode('', array_map(function ($item) { + return (string) $item; + }, $this->elements)); + } + + public function normalize(NormalizerInterface $normalizer, string $format = null, array $context = []) + { + return (string) $this; + } + + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) + { + // TODO: Implement denormalize() method. + } +} diff --git a/src/Domain/Environment/DTO/ExpressionParser.php b/src/Domain/Environment/DTO/ExpressionParser.php new file mode 100644 index 0000000..f881fd0 --- /dev/null +++ b/src/Domain/Environment/DTO/ExpressionParser.php @@ -0,0 +1,32 @@ + 0) { + $elements[] = $matches[1]; + } + + if (isset($matches[3])) { + $elements[] = new Variable($matches[3]); + } else if (isset($matches[2])) { + $elements[] = new Variable($matches[2]); + } + + $offset += strlen($matches[0]); + } + + return new Expression(...$elements); + } +} diff --git a/src/Domain/Environment/DTO/SecretValueEnvironmentVariable.php b/src/Domain/Environment/DTO/SecretValueEnvironmentVariable.php new file mode 100644 index 0000000..3f5e0d1 --- /dev/null +++ b/src/Domain/Environment/DTO/SecretValueEnvironmentVariable.php @@ -0,0 +1,25 @@ +variable = $variable; + $this->secret = $secret; + } + + public function getVariable(): Variable + { + return $this->variable; + } + + public function getSecret(): string + { + return $this->secret; + } +} diff --git a/src/Domain/Environment/DTO/Server.php b/src/Domain/Environment/DTO/Server.php new file mode 100644 index 0000000..874428f --- /dev/null +++ b/src/Domain/Environment/DTO/Server.php @@ -0,0 +1,17 @@ +hostname = $hostname; + $this->port = $port; + $this->username = $username; + } +} diff --git a/src/Domain/Environment/DTO/ValuedEnvironmentVariableInterface.php b/src/Domain/Environment/DTO/ValuedEnvironmentVariableInterface.php new file mode 100644 index 0000000..4fe294b --- /dev/null +++ b/src/Domain/Environment/DTO/ValuedEnvironmentVariableInterface.php @@ -0,0 +1,9 @@ +name = $name; + } + + public function __toString() + { + return $this->name; + } +} diff --git a/src/Platform/Console/Command/Environment/InitCommand.php b/src/Platform/Console/Command/Environment/InitCommand.php index 4d356c6..042470c 100644 --- a/src/Platform/Console/Command/Environment/InitCommand.php +++ b/src/Platform/Console/Command/Environment/InitCommand.php @@ -1,47 +1,97 @@ -wizard = new EnvironmentWizard(); + parent::__construct($name); + } + protected function configure() { $this->setDescription('Initialize the environment file in local workspace'); + + $this->wizard->configureConsoleCommand($this); } protected function execute(InputInterface $input, OutputInterface $output) { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + $format = new SymfonyStyle($input, $output); - $allLines = []; - $serverAddress = $format->askQuestion(new Question('Server of your remote directory')); - $allLines['SERVER_ADDRESS'] = $serverAddress; + $serializer = new Serializer( + [ + new PropertyNormalizer(), + ], + [ + new YamlEncoder() + ] + ); + + if ($finder->hasResults()) { + /** @var \SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + $format->error('The directory was already initialized with an environment file. You should update it using commands listed in environment:variable'); + return 0; + } + } + + $format = new SymfonyStyle($input, $output); - $depPath = $format->askQuestion(new Question('Path of your remote directory on the server')); - $allLines['DEPLOYMENT_PATH'] = $depPath; + $context = new Context( + new Deployment( + new Server( + $format->askQuestion(new Question('Please provide the SSH host of your remote environment')), + $format->askQuestion(new Question('Please provide the SSH port of your remote environment', 22)), + $format->askQuestion(new Question('Please provide the SSH user name of your remote environment', 'root')), + ), + $format->askQuestion(new Question('Please provide the path to your remote environment')), + ), + ); $envDistPath = getcwd() . '/.env.dist'; if (file_exists($envDistPath)) { $envDist = parse_ini_file($envDistPath); - foreach (array_keys($envDist) as $line) { - $lineValue = $format->askQuestion(new Question('Value of ' . $line)); - $allLines[$line] = $lineValue; + foreach (array_keys($envDist) as $name) { + $value = $format->askQuestion(new Question('Value of ' . $name)); + $variable = [$name, $value]; + $context->environmentVariables[$name] = $value; } } - $yaml = Yaml::dump($allLines); - file_put_contents('.kloud.environent.yaml', $yaml); + $format->note('Writing a new .kloud.environment.yaml file.'); + file_put_contents($workingDirectory . '/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 2, + 'yaml_indent' => 0, + 'yaml_flags' => 0 + ])); return 0; } diff --git a/src/Platform/Console/Command/Environment/Variable/GetCommand.php b/src/Platform/Console/Command/Environment/Variable/GetCommand.php new file mode 100644 index 0000000..48802cd --- /dev/null +++ b/src/Platform/Console/Command/Environment/Variable/GetCommand.php @@ -0,0 +1,98 @@ +wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Prints an environment variable'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder() + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + return 1; + } + + $variableName = $format->askQuestion(new Question('Please enter a variable name')); + + /** @var Context $context */ + $value = $context->getVariableValue($variableName); + + if (!$value) { + $format->error('This variable does not exist'); + return 0; + } + + $format->table( + ['Variable', 'Value'], + [ + [$variableName, $value], + ] + ); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/Variable/ListCommand.php b/src/Platform/Console/Command/Environment/Variable/ListCommand.php new file mode 100644 index 0000000..6fa2dc1 --- /dev/null +++ b/src/Platform/Console/Command/Environment/Variable/ListCommand.php @@ -0,0 +1,95 @@ +wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Prints the list of environment variable'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder() + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + return 1; + } + + $format->table( + ['Variable', 'Value'], + iterator_to_array((function (Context $context): \Generator { + /** @var EnvironmentVariableInterface|ValuedEnvironmentVariableInterface $variable */ + foreach ($context->environmentVariables as $variable) { + yield [ + (string) $variable->getVariable(), + ($context->getVariableValue((string) $variable->getVariable())) + ]; + } + })($context)), + ); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Stack/InitCommand.php b/src/Platform/Console/Command/Stack/InitCommand.php index f1f59d6..12be139 100644 --- a/src/Platform/Console/Command/Stack/InitCommand.php +++ b/src/Platform/Console/Command/Stack/InitCommand.php @@ -84,4 +84,4 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; } -} \ No newline at end of file +} diff --git a/src/Platform/Console/EnvironmentWizard.php b/src/Platform/Console/EnvironmentWizard.php new file mode 100644 index 0000000..ceea8ba --- /dev/null +++ b/src/Platform/Console/EnvironmentWizard.php @@ -0,0 +1,16 @@ +addOption('working-directory', null, InputOption::VALUE_OPTIONAL, 'Change the working directory in which the kloud environment file will be guessed from and written.'); + } +} From 5c58d79da0e8a2ddec2e02c99023d2b1e00c127d Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Wed, 30 Sep 2020 17:10:21 +0200 Subject: [PATCH 08/23] #12 : refacto --- src/Domain/Environment/DTO/Context.php | 43 ++++++++----------- .../Environment/VariableNotFoundException.php | 8 ++++ .../Command/Environment/InitCommand.php | 36 ++++++++++++---- .../Environment/Variable/GetCommand.php | 33 ++++++++++---- .../Environment/Variable/ListCommand.php | 14 ++++-- 5 files changed, 88 insertions(+), 46 deletions(-) create mode 100644 src/Domain/Environment/VariableNotFoundException.php diff --git a/src/Domain/Environment/DTO/Context.php b/src/Domain/Environment/DTO/Context.php index 75b930b..282961a 100644 --- a/src/Domain/Environment/DTO/Context.php +++ b/src/Domain/Environment/DTO/Context.php @@ -1,7 +1,10 @@ -environmentVariables = []; } - public function addVariables(EnvironmentVariableInterface ...$variables): void + public function addVariable(EnvironmentVariableInterface ...$variable): void { - array_push($this->environmentVariables, ...$variables); + array_push($this->environmentVariables, ...$variable); } - public function getVariableValue(string $variableName) + public function getVariable(string $variableName): EnvironmentVariableInterface { foreach ($this->environmentVariables as $variable) { - if ($variable instanceof DirectValueEnvironmentVariable) { - if ($variableName === $variable->getVariable()->__toString()) { - $value = $variable->getValue()->__toString(); - if (!$value) { - $value = ' '; - } - return $value; - } - } else if ($variable instanceof SecretValueEnvironmentVariable) { - if ($variableName === $variable->getVariable()->__toString()) { - return '**secret**'; - } - } else { - if ($variableName === $variable->getVariable()->__toString()) { - return ' '; - } + if ($variableName !== (string) $variable->getVariable()) { + continue; } + + return $variable; } + + throw new VariableNotFoundException(strtr('The variable %name% does not exist.', ['%name%' => $variableName])); } public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) @@ -52,13 +45,13 @@ public function denormalize(DenormalizerInterface $denormalizer, $data, string $ $this->environmentVariables = []; $parser = new ExpressionParser(); - foreach ($data['environmentVariables'] as $variable) { + foreach ($data['environment'] as $variable) { if (isset($variable['value'])) { $this->environmentVariables[] = new DirectValueEnvironmentVariable( new Variable($variable['name']), $parser->parse($variable['value']) ); - } else if (isset($variable['secret'])) { + } elseif (isset($variable['secret'])) { $this->environmentVariables[] = new SecretValueEnvironmentVariable( new Variable($variable['name']), $variable['secret'] @@ -75,7 +68,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null { return [ 'deployment' => $normalizer->normalize($this->deployment, $format, $context), - 'environment' => (function($variables) { + 'environment' => iterator_to_array((function ($variables) { /** @var EnvironmentVariableInterface $variable */ foreach ($variables as $variable) { if ($variable instanceof DirectValueEnvironmentVariable) { @@ -83,7 +76,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null 'name' => (string) $variable->getVariable(), 'value' => $variable->getValue(), ]; - } else if ($variable instanceof SecretValueEnvironmentVariable){ + } elseif ($variable instanceof SecretValueEnvironmentVariable) { yield [ 'name' => (string) $variable->getVariable(), 'secret' => $variable->getSecret(), @@ -94,7 +87,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null ]; } } - })($this->environmentVariables), + })($this->environmentVariables)), ]; } } diff --git a/src/Domain/Environment/VariableNotFoundException.php b/src/Domain/Environment/VariableNotFoundException.php new file mode 100644 index 0000000..8d2ed8c --- /dev/null +++ b/src/Domain/Environment/VariableNotFoundException.php @@ -0,0 +1,8 @@ +name('/^\.?kloud.environment.ya?ml$/') as $file) { $format->error('The directory was already initialized with an environment file. You should update it using commands listed in environment:variable'); + return 0; } } @@ -76,21 +85,30 @@ protected function execute(InputInterface $input, OutputInterface $output) ), ); - $envDistPath = getcwd() . '/.env.dist'; + $envDistPath = getcwd().'/.env.dist'; if (file_exists($envDistPath)) { $envDist = parse_ini_file($envDistPath); foreach (array_keys($envDist) as $name) { - $value = $format->askQuestion(new Question('Value of ' . $name)); - $variable = [$name, $value]; - $context->environmentVariables[$name] = $value; + $value = $format->askQuestion(new Question('Value of '.$name)); + + $isSecret = false; + if ($value) { + $isSecret = $format->askQuestion(new ConfirmationQuestion('Is this a secret variable ?', false)); + } + + if ($isSecret) { + $context->addVariable(new SecretValueEnvironmentVariable(new Variable($name), $value)); + } else { + $context->addVariable(new DirectValueEnvironmentVariable(new Variable($name), $value)); + } } } $format->note('Writing a new .kloud.environment.yaml file.'); - file_put_contents($workingDirectory . '/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ - 'yaml_inline' => 2, + file_put_contents($workingDirectory.'/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 4, 'yaml_indent' => 0, - 'yaml_flags' => 0 + 'yaml_flags' => 0, ])); return 0; diff --git a/src/Platform/Console/Command/Environment/Variable/GetCommand.php b/src/Platform/Console/Command/Environment/Variable/GetCommand.php index 48802cd..b7d36d1 100644 --- a/src/Platform/Console/Command/Environment/Variable/GetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/GetCommand.php @@ -1,8 +1,14 @@ -name('/^\.?kloud.environment.ya?ml$/') as $file) { try { - /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + /** @var Context $context */ $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); } catch (\Throwable $exception) { $format->error($exception->getMessage()); @@ -73,23 +79,32 @@ protected function execute(InputInterface $input, OutputInterface $output) if (!isset($context)) { $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + return 1; } $variableName = $format->askQuestion(new Question('Please enter a variable name')); - /** @var Context $context */ - $value = $context->getVariableValue($variableName); + try { + /** @var EnvironmentVariableInterface $variable */ + $variable = $context->getVariable($variableName); + } catch (VariableNotFoundException $exception) { + $format->error($exception->getMessage()); - if (!$value) { - $format->error('This variable does not exist'); - return 0; + return 1; } $format->table( ['Variable', 'Value'], [ - [$variableName, $value], + [ + $variableName, + $variable instanceof ValuedEnvironmentVariableInterface ? + $variable->getValue() : + ($variable instanceof SecretValueEnvironmentVariable ? + sprintf('SECRET: %s', $variable->getSecret()) : + null), + ], ] ); diff --git a/src/Platform/Console/Command/Environment/Variable/ListCommand.php b/src/Platform/Console/Command/Environment/Variable/ListCommand.php index 6fa2dc1..aaae839 100644 --- a/src/Platform/Console/Command/Environment/Variable/ListCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/ListCommand.php @@ -1,9 +1,12 @@ -error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + return 1; } @@ -84,7 +88,11 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($context->environmentVariables as $variable) { yield [ (string) $variable->getVariable(), - ($context->getVariableValue((string) $variable->getVariable())) + $variable instanceof ValuedEnvironmentVariableInterface ? + $variable->getValue() : + ($variable instanceof SecretValueEnvironmentVariable ? + sprintf('SECRET: %s', $variable->getSecret()) : + null), ]; } })($context)), From 2e5bdf5b9194c34cd7196339e2a87f564dbc4803 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 2 Oct 2020 09:21:41 +0200 Subject: [PATCH 09/23] #12 : Add Set & Unset commands + changed PHP requirements + small cs fixer --- README.md | 5 + bin/kloud | 12 +- src/Domain/Environment/DTO/Context.php | 16 +- src/Domain/Environment/DTO/Deployment.php | 4 +- .../DTO/DirectValueEnvironmentVariable.php | 9 +- .../Environment/DTO/EnvironmentVariable.php | 4 +- .../DTO/EnvironmentVariableInterface.php | 4 +- src/Domain/Environment/DTO/Expression.php | 4 +- .../Environment/DTO/ExpressionParser.php | 6 +- .../DTO/SecretValueEnvironmentVariable.php | 9 +- src/Domain/Environment/DTO/Server.php | 4 +- .../ValuedEnvironmentVariableInterface.php | 10 +- src/Domain/Environment/DTO/Variable.php | 4 +- .../Environment/Variable/SetCommand.php | 170 ++++++++++++++++++ .../Environment/Variable/UnsetCommand.php | 107 +++++++++++ 15 files changed, 353 insertions(+), 15 deletions(-) create mode 100644 src/Platform/Console/Command/Environment/Variable/SetCommand.php create mode 100644 src/Platform/Console/Command/Environment/Variable/UnsetCommand.php diff --git a/README.md b/README.md index c4deee8..72d6c4f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc > ⚠️ Nota: Those stacks are not suited for production hosting, but to provide an environment based on Docker as identical as possible to OroCloud on a personal computer. +* [Requirements](#requirements) * [Installation](#installation) * [Usage](#usage) * [Frequently Asked Questions](#frequently-asked-questions) @@ -15,6 +16,10 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc * [Marello](#marello) * [Middleware](#middleware) +- Requirements: +--- +- PHP 7.4 + Installation --- diff --git a/bin/kloud b/bin/kloud index b47f987..c760b98 100755 --- a/bin/kloud +++ b/bin/kloud @@ -1,8 +1,8 @@ #!/usr/bin/env php addCommands([ (new Command\Environment\Variable\GetCommand( Command\Environment\Variable\GetCommand::$defaultName, )), + + (new Command\Environment\Variable\SetCommand( + Command\Environment\Variable\SetCommand::$defaultName, + )), + + (new Command\Environment\Variable\UnsetCommand( + Command\Environment\Variable\UnsetCommand::$defaultName, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Domain/Environment/DTO/Context.php b/src/Domain/Environment/DTO/Context.php index 282961a..5ff19b2 100644 --- a/src/Domain/Environment/DTO/Context.php +++ b/src/Domain/Environment/DTO/Context.php @@ -39,6 +39,20 @@ public function getVariable(string $variableName): EnvironmentVariableInterface throw new VariableNotFoundException(strtr('The variable %name% does not exist.', ['%name%' => $variableName])); } + public function setVariable(EnvironmentVariableInterface $newVariable) + { + $i = 0; + foreach ($this->environmentVariables as $variable) { + if ((string) $newVariable->getVariable() !== (string) $variable->getVariable()) { + ++$i; + continue; + } + $this->environmentVariables[$i] = $newVariable; + + return; + } + } + public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) { $this->deployment = $denormalizer->denormalize($data['deployment'], Deployment::class, $format, $context); @@ -49,7 +63,7 @@ public function denormalize(DenormalizerInterface $denormalizer, $data, string $ if (isset($variable['value'])) { $this->environmentVariables[] = new DirectValueEnvironmentVariable( new Variable($variable['name']), - $parser->parse($variable['value']) + $variable['value'] ); } elseif (isset($variable['secret'])) { $this->environmentVariables[] = new SecretValueEnvironmentVariable( diff --git a/src/Domain/Environment/DTO/Deployment.php b/src/Domain/Environment/DTO/Deployment.php index f626ff2..ff3c42c 100644 --- a/src/Domain/Environment/DTO/Deployment.php +++ b/src/Domain/Environment/DTO/Deployment.php @@ -1,4 +1,6 @@ -value; } + + public function setValue(string $value) + { + $this->value = $value; + } } diff --git a/src/Domain/Environment/DTO/EnvironmentVariable.php b/src/Domain/Environment/DTO/EnvironmentVariable.php index 323966c..bbe038c 100644 --- a/src/Domain/Environment/DTO/EnvironmentVariable.php +++ b/src/Domain/Environment/DTO/EnvironmentVariable.php @@ -1,4 +1,6 @@ -secret; } + + public function setSecret(string $secret) + { + $this->secret = $secret; + } } diff --git a/src/Domain/Environment/DTO/Server.php b/src/Domain/Environment/DTO/Server.php index 874428f..c63f8c8 100644 --- a/src/Domain/Environment/DTO/Server.php +++ b/src/Domain/Environment/DTO/Server.php @@ -1,4 +1,6 @@ -wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Prints an environment variable'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $variableName = $format->askQuestion(new Question('Please enter a variable name')); + + try { + /** @var EnvironmentVariableInterface $variable */ + $variable = $context->getVariable($variableName); + } catch (VariableNotFoundException $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + $format->table( + ['Variable', 'Value'], + [ + [ + $variableName, + $variable instanceof ValuedEnvironmentVariableInterface ? + $variable->getValue() : + ($variable instanceof SecretValueEnvironmentVariable ? + sprintf('SECRET: %s', $variable->getSecret()) : + null), + ], + ] + ); + + // If value is empty, $variable becomes/stay an EnvironmentVariable without any value. + if (!$value = $this->verifyValue($context, $format, $variable)) { + $this->sendResponse($context, $format, $serializer, $workingDirectory); + + return 0; + } + + $isSecret = $format->askQuestion(new ConfirmationQuestion('Is this a secret variable ?', false)); + + // Test $variable type and potentially change it according to the answer + if ($variable instanceof ValuedEnvironmentVariableInterface) { + if ($isSecret) { + $context->setVariable(new SecretValueEnvironmentVariable($variable->getVariable(), $value)); + } else { + $variable->setValue($value); + } + } + if ($variable instanceof SecretValueEnvironmentVariable) { + if ($isSecret) { + $variable->setSecret($value); + } else { + $context->setVariable(new DirectValueEnvironmentVariable($variable->getVariable(), $value)); + } + } + if ($variable instanceof EnvironmentVariable) { + if ($isSecret) { + $context->setVariable(new SecretValueEnvironmentVariable($variable->getVariable(), $value)); + } else { + $context->setVariable(new DirectValueEnvironmentVariable($variable->getVariable(), $value)); + } + } + $this->sendResponse($context, $format, $serializer, $workingDirectory); + + return 0; + } + + private function sendResponse(Context $context, SymfonyStyle $format, Serializer $serializer, string $workingDirectory): void + { + $format->success('Variable was successfully changed'); + file_put_contents($workingDirectory.'/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 4, + 'yaml_indent' => 0, + 'yaml_flags' => 0, + ])); + } + + private function verifyValue(Context $context, SymfonyStyle $format, EnvironmentVariableInterface $variable): ?string + { + if (!$value = $format->askQuestion(new Question('Please provide the new value'))) { + $context->setVariable(new EnvironmentVariable($variable->getVariable())); + + return null; + } + + return $value; + } +} diff --git a/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php new file mode 100644 index 0000000..68bda77 --- /dev/null +++ b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php @@ -0,0 +1,107 @@ +wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Prints an environment variable'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $variableName = $format->askQuestion(new Question('Please enter the variable to unset')); + + try { + /** @var EnvironmentVariableInterface $variable */ + $variable = $context->getVariable($variableName); + } catch (VariableNotFoundException $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + $context->setVariable(new EnvironmentVariable($variable->getVariable())); + + $format->success('Variable was successfully unset'); + file_put_contents($workingDirectory.'/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 4, + 'yaml_indent' => 0, + 'yaml_flags' => 0, + ])); + + return 0; + } +} From 3174ade5cefeff729077fabe24ba4e3fbd31af75 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 2 Oct 2020 09:23:38 +0200 Subject: [PATCH 10/23] #12 : fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72d6c4f..5080f64 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc * [Marello](#marello) * [Middleware](#middleware) -- Requirements: +Requirements: --- - PHP 7.4 From 890f79b9bd2a360ff51f3c04a7f19cd1bf77763b Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Tue, 6 Oct 2020 12:22:53 +0200 Subject: [PATCH 11/23] #12 : Add deploy, destroy and rsync commands --- README.md | 1 + bin/kloud | 15 ++ composer.json | 3 +- composer.lock | 241 ++++++++++++------ src/Domain/Environment/DTO/Context.php | 2 +- .../VariableNotFoundException.php | 3 +- .../Command/Environment/DeployCommand.php | 146 +++++++++++ .../Command/Environment/DestroyCommand.php | 124 +++++++++ .../Command/Environment/RsyncCommand.php | 130 ++++++++++ .../Environment/Variable/GetCommand.php | 2 +- .../Environment/Variable/SetCommand.php | 2 +- .../Environment/Variable/UnsetCommand.php | 2 +- 12 files changed, 585 insertions(+), 86 deletions(-) rename src/Domain/Environment/{ => Exception}/VariableNotFoundException.php (56%) create mode 100644 src/Platform/Console/Command/Environment/DeployCommand.php create mode 100644 src/Platform/Console/Command/Environment/DestroyCommand.php create mode 100644 src/Platform/Console/Command/Environment/RsyncCommand.php diff --git a/README.md b/README.md index 5080f64..e49e37f 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ This project aims at building your Docker stack for [OroCommerce](https://oroinc Requirements: --- - PHP 7.4 +- rsync (for environment commands) Installation --- diff --git a/bin/kloud b/bin/kloud index c760b98..313862c 100755 --- a/bin/kloud +++ b/bin/kloud @@ -79,6 +79,21 @@ $app->addCommands([ (new Command\Environment\Variable\UnsetCommand( Command\Environment\Variable\UnsetCommand::$defaultName, )), + + (new Command\Environment\DeployCommand( + Command\Environment\DeployCommand::$defaultName, + $app, + )), + + (new Command\Environment\DestroyCommand( + Command\Environment\DestroyCommand::$defaultName, + $app, + )), + + (new Command\Environment\RsyncCommand( + Command\Environment\RsyncCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/composer.json b/composer.json index 75f1cbd..0694776 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "symfony/property-access": "^5.0", "symfony/serializer": "^5.0", "symfony/yaml": "^5.0", - "splitbrain/php-archive": "^1.1" + "splitbrain/php-archive": "^1.1", + "deployer/deployer": "^6.8" }, "require-dev": { "friends-of-phpspec/phpspec-code-coverage": "^4.0", diff --git a/composer.lock b/composer.lock index 9467444..740103f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "525a32433d3859897ef760bac6fa763b", + "content-hash": "30eee74a5479099f32df904d397eae35", "packages": [ { "name": "composer/ca-bundle", @@ -123,6 +123,115 @@ ], "time": "2020-01-13T12:06:48+00:00" }, + { + "name": "deployer/deployer", + "version": "v6.8.0", + "source": { + "type": "git", + "url": "https://github.com/deployphp/deployer.git", + "reference": "4e243a64ed61e779fbb31c5a74e258a8e52fdaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/deployphp/deployer/zipball/4e243a64ed61e779fbb31c5a74e258a8e52fdaff", + "reference": "4e243a64ed61e779fbb31c5a74e258a8e52fdaff", + "shasum": "" + }, + "require": { + "deployer/phar-update": "~2.2", + "php": "^7.2", + "pimple/pimple": "~3.0", + "symfony/console": "~2.7|~3.0|~4.0|~5.0", + "symfony/process": "~2.7|~3.0|~4.0|~5.0", + "symfony/yaml": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "phpunit/phpunit": "^8" + }, + "bin": [ + "bin/dep" + ], + "type": "library", + "autoload": { + "psr-4": { + "Deployer\\": "src/" + }, + "files": [ + "src/Support/helpers.php", + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anton Medvedev", + "email": "anton@medv.io" + } + ], + "description": "Deployment Tool", + "homepage": "https://deployer.org", + "time": "2020-04-25T16:05:31+00:00" + }, + { + "name": "deployer/phar-update", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/deployphp/phar-update.git", + "reference": "9ad07422f2cd43a1382ee8e134bdcd3a374848e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/deployphp/phar-update/zipball/9ad07422f2cd43a1382ee8e134bdcd3a374848e3", + "reference": "9ad07422f2cd43a1382ee8e134bdcd3a374848e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/console": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "mikey179/vfsstream": "1.1.0", + "phpunit/phpunit": "3.7.*", + "symfony/process": "~2.7|~3.0|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Deployer\\Component\\PharUpdate\\": "src/", + "Deployer\\Component\\PHPUnit\\": "src/PHPUnit/", + "Deployer\\Component\\Version\\": "src/Version/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Herrera", + "email": "kevin@herrera.io", + "homepage": "http://kevin.herrera.io" + }, + { + "name": "Anton Medvedev", + "email": "anton@medv.io", + "homepage": "https://medv.io" + } + ], + "description": "Integrates Phar Update to Symfony Console.", + "homepage": "https://github.com/deployphp/phar-update", + "keywords": [ + "console", + "phar", + "update" + ], + "abandoned": true, + "time": "2019-12-12T13:45:57+00:00" + }, { "name": "padraic/humbug_get_contents", "version": "1.1.2", @@ -242,8 +351,59 @@ "self-update", "update" ], + "abandoned": true, "time": "2018-03-30T12:52:15+00:00" }, + { + "name": "pimple/pimple", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/e55d12f9d6a0e7f9c85992b73df1267f46279930", + "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.4|^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2020-03-03T09:12:48+00:00" + }, { "name": "psr/container", "version": "1.0.0", @@ -708,20 +868,6 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-24T15:05:31+00:00" }, { @@ -771,20 +917,6 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-14T07:43:07+00:00" }, { @@ -1080,20 +1212,6 @@ "portable", "shim" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-03-03T16:59:03+00:00" }, { @@ -1292,20 +1410,6 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-29T10:07:09+00:00" }, { @@ -1469,12 +1573,6 @@ "Xdebug", "performance" ], - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - } - ], "time": "2020-03-01T12:26:26+00:00" }, { @@ -2804,20 +2902,6 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-22T20:09:08+00:00" }, { @@ -3247,6 +3331,5 @@ "php": "^7.4", "ext-json": "*" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": [] } diff --git a/src/Domain/Environment/DTO/Context.php b/src/Domain/Environment/DTO/Context.php index 5ff19b2..59f171e 100644 --- a/src/Domain/Environment/DTO/Context.php +++ b/src/Domain/Environment/DTO/Context.php @@ -4,7 +4,7 @@ namespace Kiboko\Cloud\Domain\Environment\DTO; -use Kiboko\Cloud\Domain\Environment\VariableNotFoundException; +use Kiboko\Cloud\Domain\Environment\Exception\VariableNotFoundException; use Symfony\Component\Serializer\Normalizer\DenormalizableInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizableInterface; diff --git a/src/Domain/Environment/VariableNotFoundException.php b/src/Domain/Environment/Exception/VariableNotFoundException.php similarity index 56% rename from src/Domain/Environment/VariableNotFoundException.php rename to src/Domain/Environment/Exception/VariableNotFoundException.php index 8d2ed8c..b9a534d 100644 --- a/src/Domain/Environment/VariableNotFoundException.php +++ b/src/Domain/Environment/Exception/VariableNotFoundException.php @@ -1,8 +1,7 @@ console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Deploy the application to a remote server using rsync and initialize docker containers'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + $deployer['log_handler'] = function ($deployer) { + return !empty($deployer->config['log_file']) + ? new FileHandler($deployer->config['log_file']) + : new NullHandler(); + }; + $deployer['logger'] = function ($deployer) { + return new Logger($deployer['log_handler']); + }; + $rsync = new Rsync(new ProcessOutputPrinter($output, $deployer['logger'])); + + $hosts = []; + $tasks = []; + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + array_push($hosts, $host); + + $destination = $host->getUser().'@'.$host->getHostname().':'.$context->deployment->path; + + try { + $format->note('Syncing remote directory with local directory'); + $rsync->call($host->getHostname(), $workingDirectory, $destination); + $format->success('Remote directory synced with local directory'); + } catch (ProcessFailedException $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + + $command = 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose up --no-start'; + + array_push($tasks, new Task('docker:up', function () use ($command, $host) { + run($command); + })); + + $seriesExecutor = new SeriesExecutor($input, $output, new Informer(new OutputWatcher($output))); + $seriesExecutor->run($tasks, $hosts); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/DestroyCommand.php b/src/Platform/Console/Command/Environment/DestroyCommand.php new file mode 100644 index 0000000..27faa0a --- /dev/null +++ b/src/Platform/Console/Command/Environment/DestroyCommand.php @@ -0,0 +1,124 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Destroy the Docker infrastructure with associated volumes and remove remote directory'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + + $hosts = []; + $tasks = []; + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + array_push($hosts, $host); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + + $commands = [ + 'docker:down' => 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose down -v', + 'directory:remove' => 'cd '.$context->deployment->path.' && rm -rf '.$projectName, + ]; + + foreach ($commands as $key => $value) { + array_push($tasks, new Task($key, function () use ($value, $host) { + run($value); + })); + } + + $seriesExecutor = new SeriesExecutor($input, $output, new Informer(new OutputWatcher($output))); + $seriesExecutor->run($tasks, $hosts); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/RsyncCommand.php b/src/Platform/Console/Command/Environment/RsyncCommand.php new file mode 100644 index 0000000..2b8bf50 --- /dev/null +++ b/src/Platform/Console/Command/Environment/RsyncCommand.php @@ -0,0 +1,130 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Deploy the application to a remote server using rsync and initialize docker containers'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + $deployer['log_handler'] = function ($deployer) { + return !empty($deployer->config['log_file']) + ? new FileHandler($deployer->config['log_file']) + : new NullHandler(); + }; + $deployer['logger'] = function ($deployer) { + return new Logger($deployer['log_handler']); + }; + $rsync = new Rsync(new ProcessOutputPrinter($output, $deployer['logger'])); + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + + $destination = $host->getUser().'@'.$host->getHostname().':'.$context->deployment->path; + $config = [ + 'options' => [ + '--delete', + ], + ]; + + try { + $format->note('Syncing remote directory with local directory'); + $rsync->call($host->getHostname(), $workingDirectory, $destination, $config); + $format->success('Remote directory synced with local directory'); + } catch (ProcessFailedException $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/Variable/GetCommand.php b/src/Platform/Console/Command/Environment/Variable/GetCommand.php index b7d36d1..8788fd4 100644 --- a/src/Platform/Console/Command/Environment/Variable/GetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/GetCommand.php @@ -7,7 +7,7 @@ use Kiboko\Cloud\Domain\Environment\DTO\Context; use Kiboko\Cloud\Domain\Environment\DTO\SecretValueEnvironmentVariable; use Kiboko\Cloud\Domain\Environment\DTO\ValuedEnvironmentVariableInterface; -use Kiboko\Cloud\Domain\Environment\VariableNotFoundException; +use Kiboko\Cloud\Domain\Environment\Exception\VariableNotFoundException; use Kiboko\Cloud\Domain\Stack\Compose\EnvironmentVariableInterface; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; use Symfony\Component\Console\Command\Command; diff --git a/src/Platform/Console/Command/Environment/Variable/SetCommand.php b/src/Platform/Console/Command/Environment/Variable/SetCommand.php index d319ca0..bfbaa58 100644 --- a/src/Platform/Console/Command/Environment/Variable/SetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/SetCommand.php @@ -10,7 +10,7 @@ use Kiboko\Cloud\Domain\Environment\DTO\EnvironmentVariableInterface; use Kiboko\Cloud\Domain\Environment\DTO\SecretValueEnvironmentVariable; use Kiboko\Cloud\Domain\Environment\DTO\ValuedEnvironmentVariableInterface; -use Kiboko\Cloud\Domain\Environment\VariableNotFoundException; +use Kiboko\Cloud\Domain\Environment\Exception\VariableNotFoundException; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php index 68bda77..cdcdfc1 100644 --- a/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php @@ -7,7 +7,7 @@ use Kiboko\Cloud\Domain\Environment\DTO\Context; use Kiboko\Cloud\Domain\Environment\DTO\EnvironmentVariable; use Kiboko\Cloud\Domain\Environment\DTO\EnvironmentVariableInterface; -use Kiboko\Cloud\Domain\Environment\VariableNotFoundException; +use Kiboko\Cloud\Domain\Environment\Exception\VariableNotFoundException; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; From ae22456cebbe6e5eb83fd65f08eb7f9e2fc7d1fc Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Tue, 6 Oct 2020 15:59:23 +0200 Subject: [PATCH 12/23] #12 : Add cache clear, start and stop commands --- bin/kloud | 16 +++ .../Environment/Cache/ClearCommand.php | 127 ++++++++++++++++++ .../Command/Environment/DeployCommand.php | 4 +- .../Command/Environment/DestroyCommand.php | 4 +- .../Command/Environment/RsyncCommand.php | 2 +- .../Command/Environment/StartCommand.php | 119 ++++++++++++++++ .../Command/Environment/StopCommand.php | 119 ++++++++++++++++ .../Environment/Variable/GetCommand.php | 2 +- .../Environment/Variable/ListCommand.php | 2 +- .../Environment/Variable/SetCommand.php | 2 +- .../Environment/Variable/UnsetCommand.php | 2 +- 11 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 src/Platform/Console/Command/Environment/Cache/ClearCommand.php create mode 100644 src/Platform/Console/Command/Environment/StartCommand.php create mode 100644 src/Platform/Console/Command/Environment/StopCommand.php diff --git a/bin/kloud b/bin/kloud index 313862c..adc17dd 100755 --- a/bin/kloud +++ b/bin/kloud @@ -90,10 +90,26 @@ $app->addCommands([ $app, )), + (new Command\Environment\StartCommand( + Command\Environment\StartCommand::$defaultName, + $app, + )), + + + (new Command\Environment\StopCommand( + Command\Environment\StopCommand::$defaultName, + $app, + )), + (new Command\Environment\RsyncCommand( Command\Environment\RsyncCommand::$defaultName, $app, )), + + (new Command\Environment\Cache\ClearCommand( + Command\Environment\Cache\ClearCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/Cache/ClearCommand.php b/src/Platform/Console/Command/Environment/Cache/ClearCommand.php new file mode 100644 index 0000000..b497d2d --- /dev/null +++ b/src/Platform/Console/Command/Environment/Cache/ClearCommand.php @@ -0,0 +1,127 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Clear cache and restart FPM service'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $env = $format->askQuestion(new ChoiceQuestion('For what environment ?', ['prod', 'dev', 'test'], 'prod')); + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + + $hosts = []; + $tasks = []; + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + array_push($hosts, $host); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + + $commands = [ + 'cache:clear' => 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose exec -T sh bin/console cache:clear --env='.$env, + 'docker:restart-fpm' => 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose restart fpm', + ]; + + foreach ($commands as $key => $value) { + array_push($tasks, new Task($key, function () use ($value, $host) { + run($value); + })); + } + + $seriesExecutor = new SeriesExecutor($input, $output, new Informer(new OutputWatcher($output))); + $seriesExecutor->run($tasks, $hosts); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/DeployCommand.php b/src/Platform/Console/Command/Environment/DeployCommand.php index 4b34de8..00b21c8 100644 --- a/src/Platform/Console/Command/Environment/DeployCommand.php +++ b/src/Platform/Console/Command/Environment/DeployCommand.php @@ -13,6 +13,7 @@ use Deployer\Logger\Handler\FileHandler; use Deployer\Logger\Handler\NullHandler; use Deployer\Logger\Logger; +use function Deployer\run; use Deployer\Task\Task; use Deployer\Utility\ProcessOutputPrinter; use Deployer\Utility\Rsync; @@ -30,7 +31,6 @@ use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; -use function Deployer\run; final class DeployCommand extends Command { @@ -48,7 +48,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Deploy the application to a remote server using rsync and initialize docker containers'); + $this->setDescription('Deploy the application to a remote server using rsync and initialize docker services'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/DestroyCommand.php b/src/Platform/Console/Command/Environment/DestroyCommand.php index 27faa0a..4ea0488 100644 --- a/src/Platform/Console/Command/Environment/DestroyCommand.php +++ b/src/Platform/Console/Command/Environment/DestroyCommand.php @@ -10,6 +10,7 @@ use Deployer\Deployer; use Deployer\Executor\SeriesExecutor; use Deployer\Host\Host; +use function Deployer\run; use Deployer\Task\Task; use Kiboko\Cloud\Domain\Environment\DTO\Context; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; @@ -24,7 +25,6 @@ use Symfony\Component\Serializer\Normalizer\CustomNormalizer; use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; -use function Deployer\run; final class DestroyCommand extends Command { @@ -42,7 +42,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Destroy the Docker infrastructure with associated volumes and remove remote directory'); + $this->setDescription('Destroy the docker infrastructure with associated volumes and remove remote directory'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/RsyncCommand.php b/src/Platform/Console/Command/Environment/RsyncCommand.php index 2b8bf50..4de23c5 100644 --- a/src/Platform/Console/Command/Environment/RsyncCommand.php +++ b/src/Platform/Console/Command/Environment/RsyncCommand.php @@ -43,7 +43,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Deploy the application to a remote server using rsync and initialize docker containers'); + $this->setDescription('Synchronize remote directory according to local directory'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/StartCommand.php b/src/Platform/Console/Command/Environment/StartCommand.php new file mode 100644 index 0000000..c12b3fa --- /dev/null +++ b/src/Platform/Console/Command/Environment/StartCommand.php @@ -0,0 +1,119 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Start docker services on the remote server'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + + $hosts = []; + $tasks = []; + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + array_push($hosts, $host); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + + $command = 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose start'; + + array_push($tasks, new Task('docker:start', function () use ($command, $host) { + run($command); + })); + + $seriesExecutor = new SeriesExecutor($input, $output, new Informer(new OutputWatcher($output))); + $seriesExecutor->run($tasks, $hosts); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/StopCommand.php b/src/Platform/Console/Command/Environment/StopCommand.php new file mode 100644 index 0000000..3164bed --- /dev/null +++ b/src/Platform/Console/Command/Environment/StopCommand.php @@ -0,0 +1,119 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Stoptty docker services on the remote server'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var \Kiboko\Cloud\Domain\Stack\DTO\Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + + $hosts = []; + $tasks = []; + + /** @var Context $context */ + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + array_push($hosts, $host); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + + $command = 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose stop'; + + array_push($tasks, new Task('docker:stop', function () use ($command, $host) { + run($command); + })); + + $seriesExecutor = new SeriesExecutor($input, $output, new Informer(new OutputWatcher($output))); + $seriesExecutor->run($tasks, $hosts); + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/Variable/GetCommand.php b/src/Platform/Console/Command/Environment/Variable/GetCommand.php index 8788fd4..37298a0 100644 --- a/src/Platform/Console/Command/Environment/Variable/GetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/GetCommand.php @@ -36,7 +36,7 @@ public function __construct(?string $name) protected function configure() { - $this->setDescription('Prints an environment variable'); + $this->setDescription('Print an environment variable value'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/Variable/ListCommand.php b/src/Platform/Console/Command/Environment/Variable/ListCommand.php index aaae839..64e301d 100644 --- a/src/Platform/Console/Command/Environment/Variable/ListCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/ListCommand.php @@ -34,7 +34,7 @@ public function __construct(?string $name) protected function configure() { - $this->setDescription('Prints the list of environment variable'); + $this->setDescription('Print the list of environment variables and their value'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/Variable/SetCommand.php b/src/Platform/Console/Command/Environment/Variable/SetCommand.php index bfbaa58..957bcd4 100644 --- a/src/Platform/Console/Command/Environment/Variable/SetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/SetCommand.php @@ -39,7 +39,7 @@ public function __construct(?string $name) protected function configure() { - $this->setDescription('Prints an environment variable'); + $this->setDescription('Change an environment variable value'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php index cdcdfc1..03af391 100644 --- a/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php +++ b/src/Platform/Console/Command/Environment/Variable/UnsetCommand.php @@ -35,7 +35,7 @@ public function __construct(?string $name) protected function configure() { - $this->setDescription('Prints an environment variable'); + $this->setDescription('Unset an environment variable value'); $this->wizard->configureConsoleCommand($this); } From a7ba45a679687e0645e8e541673d010847ccd47d Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Tue, 17 Nov 2020 09:32:39 +0100 Subject: [PATCH 13/23] #12 : Run `composer update nothing` after merge --- composer.lock | 97 ++------------------------------------------------- 1 file changed, 2 insertions(+), 95 deletions(-) diff --git a/composer.lock b/composer.lock index 1649b28..b9744b6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "30eee74a5479099f32df904d397eae35", + "content-hash": "218e72c75e07b49f15a0b7d9ab9cf7c1", "packages": [ { "name": "composer/ca-bundle", @@ -868,20 +868,6 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-24T15:05:31+00:00" }, { @@ -931,20 +917,6 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-14T07:43:07+00:00" }, { @@ -1244,20 +1216,6 @@ "portable", "shim" ], - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-10-23T14:02:19+00:00" }, { @@ -1456,20 +1414,6 @@ ], "description": "Symfony Serializer Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-29T10:07:09+00:00" }, { @@ -1689,12 +1633,6 @@ "Xdebug", "performance" ], - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - } - ], "time": "2020-03-01T12:26:26+00:00" }, { @@ -2183,12 +2121,6 @@ "object", "object graph" ], - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], "time": "2020-06-29T13:22:24+00:00" }, { @@ -3056,16 +2988,6 @@ "testing", "xunit" ], - "funding": [ - { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-06-22T07:06:58+00:00" }, { @@ -3557,20 +3479,6 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], "time": "2020-02-22T20:09:08+00:00" }, { @@ -3999,6 +3907,5 @@ "php": "^7.4", "ext-json": "*" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": [] } From 8d73483d054a4a67eb8d349b472a3ff3f1c5aac5 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 10:16:43 +0100 Subject: [PATCH 14/23] #12 : fix yaml indent and default dbms to null --- src/Domain/Stack/DTO/Context.php | 4 ++-- src/Platform/Console/Command/Stack/InitCommand.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Domain/Stack/DTO/Context.php b/src/Domain/Stack/DTO/Context.php index b5ba27f..462a087 100644 --- a/src/Domain/Stack/DTO/Context.php +++ b/src/Domain/Stack/DTO/Context.php @@ -24,7 +24,7 @@ public function __construct( string $phpVersion, ?string $application = null, ?string $applicationVersion = null, - ?string $dbms = self::DBMS_POSTGRESQL, + ?string $dbms = null, ?bool $isEnterpriseEdition = false ) { $this->phpVersion = $phpVersion; @@ -80,4 +80,4 @@ public function getImagesRegex(): string $this->dbms ); } -} \ No newline at end of file +} diff --git a/src/Platform/Console/Command/Stack/InitCommand.php b/src/Platform/Console/Command/Stack/InitCommand.php index 12be139..c0fa093 100644 --- a/src/Platform/Console/Command/Stack/InitCommand.php +++ b/src/Platform/Console/Command/Stack/InitCommand.php @@ -70,7 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $format->note('Writing a new .kloud.yaml file.'); file_put_contents($workingDirectory . '/.kloud.yaml', $serializer->serialize($context, 'yaml', [ 'yaml_inline' => 2, - 'yaml_indent' => 2, + 'yaml_indent' => 0, 'yaml_flags' => 0 ])); From f4bc6e24d3a5c72bcef04bbc5deb5df866061369 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 10:19:25 +0100 Subject: [PATCH 15/23] #12 : Add database:dump and variable:add commands --- bin/kloud | 10 +- src/Domain/Environment/DTO/Context.php | 8 +- src/Domain/Environment/DTO/Database.php | 19 ++ src/Domain/Environment/DTO/Deployment.php | 4 +- .../DTO/DirectValueEnvironmentVariable.php | 3 + .../Environment/Database/DumpCommand.php | 178 ++++++++++++++++++ .../Command/Environment/DestroyCommand.php | 5 +- .../Command/Environment/InitCommand.php | 6 + .../Environment/Variable/AddCommand.php | 109 +++++++++++ 9 files changed, 335 insertions(+), 7 deletions(-) create mode 100644 src/Domain/Environment/DTO/Database.php create mode 100644 src/Platform/Console/Command/Environment/Database/DumpCommand.php create mode 100644 src/Platform/Console/Command/Environment/Variable/AddCommand.php diff --git a/bin/kloud b/bin/kloud index adc17dd..67aa541 100755 --- a/bin/kloud +++ b/bin/kloud @@ -76,6 +76,10 @@ $app->addCommands([ Command\Environment\Variable\SetCommand::$defaultName, )), + (new Command\Environment\Variable\AddCommand( + Command\Environment\Variable\AddCommand::$defaultName, + )), + (new Command\Environment\Variable\UnsetCommand( Command\Environment\Variable\UnsetCommand::$defaultName, )), @@ -95,7 +99,6 @@ $app->addCommands([ $app, )), - (new Command\Environment\StopCommand( Command\Environment\StopCommand::$defaultName, $app, @@ -110,6 +113,11 @@ $app->addCommands([ Command\Environment\Cache\ClearCommand::$defaultName, $app, )), + + (new Command\Environment\Database\DumpCommand( + Command\Environment\Database\DumpCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Domain/Environment/DTO/Context.php b/src/Domain/Environment/DTO/Context.php index 59f171e..b975a6d 100644 --- a/src/Domain/Environment/DTO/Context.php +++ b/src/Domain/Environment/DTO/Context.php @@ -13,11 +13,13 @@ class Context implements NormalizableInterface, DenormalizableInterface { public ?Deployment $deployment; + public ?Database $database; public iterable $environmentVariables; - public function __construct(?Deployment $deployment = null) + public function __construct(?Deployment $deployment = null, ?Database $database = null) { $this->deployment = $deployment; + $this->database = $database; $this->environmentVariables = []; } @@ -39,7 +41,7 @@ public function getVariable(string $variableName): EnvironmentVariableInterface throw new VariableNotFoundException(strtr('The variable %name% does not exist.', ['%name%' => $variableName])); } - public function setVariable(EnvironmentVariableInterface $newVariable) + public function setVariable(EnvironmentVariableInterface $newVariable): void { $i = 0; foreach ($this->environmentVariables as $variable) { @@ -56,6 +58,7 @@ public function setVariable(EnvironmentVariableInterface $newVariable) public function denormalize(DenormalizerInterface $denormalizer, $data, string $format = null, array $context = []) { $this->deployment = $denormalizer->denormalize($data['deployment'], Deployment::class, $format, $context); + $this->database = $denormalizer->denormalize($data['database'], Database::class, $format, $context); $this->environmentVariables = []; $parser = new ExpressionParser(); @@ -82,6 +85,7 @@ public function normalize(NormalizerInterface $normalizer, string $format = null { return [ 'deployment' => $normalizer->normalize($this->deployment, $format, $context), + 'database' => $normalizer->normalize($this->database, $format, $context), 'environment' => iterator_to_array((function ($variables) { /** @var EnvironmentVariableInterface $variable */ foreach ($variables as $variable) { diff --git a/src/Domain/Environment/DTO/Database.php b/src/Domain/Environment/DTO/Database.php new file mode 100644 index 0000000..8e84b70 --- /dev/null +++ b/src/Domain/Environment/DTO/Database.php @@ -0,0 +1,19 @@ +databaseName = $databaseName; + $this->username = $username; + $this->password = $password; + } +} diff --git a/src/Domain/Environment/DTO/Deployment.php b/src/Domain/Environment/DTO/Deployment.php index ff3c42c..0ef1dc6 100644 --- a/src/Domain/Environment/DTO/Deployment.php +++ b/src/Domain/Environment/DTO/Deployment.php @@ -6,8 +6,8 @@ class Deployment { - public Server $server; - public string $path; + public ?Server $server; + public ?string $path; public function __construct(?Server $server = null, ?string $path = null) { diff --git a/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php b/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php index 28f9d6f..8822746 100644 --- a/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php +++ b/src/Domain/Environment/DTO/DirectValueEnvironmentVariable.php @@ -21,6 +21,9 @@ public function getVariable(): Variable return $this->variable; } + /** + * @return int|Expression|Variable|string + */ public function getValue() { return $this->value; diff --git a/src/Platform/Console/Command/Environment/Database/DumpCommand.php b/src/Platform/Console/Command/Environment/Database/DumpCommand.php new file mode 100644 index 0000000..96fa1a8 --- /dev/null +++ b/src/Platform/Console/Command/Environment/Database/DumpCommand.php @@ -0,0 +1,178 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Dumps the database in the current state'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $environmentFile) { + try { + /** @var EnvironmentContext $environementContext */ + $environementContext = $serializer->deserialize($environmentFile->getContents(), EnvironmentContext::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + foreach ($finder->name('/^\.?kloud.ya?ml$/') as $stackFile) { + try { + /** @var StackContext $stackContext */ + $stackContext = $serializer->deserialize($stackFile->getContents(), StackContext::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($environementContext)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $application = new Application($this->console->getName()); + $deployer = new Deployer($application); + $deployer['output'] = $output; + $deployer['log_handler'] = function ($deployer) { + return !empty($deployer->config['log_file']) + ? new FileHandler($deployer->config['log_file']) + : new NullHandler(); + }; + $deployer['logger'] = function ($deployer) { + return new Logger($deployer['log_handler']); + }; + + $host = new Host($environementContext->deployment->server->hostname); + $host->port($environementContext->deployment->server->port); + $host->user($environementContext->deployment->server->username); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + $remoteProjectPath = $environementContext->deployment->path.'/'.$projectName; + + $sqlService = $format->askQuestion(new Question('What is the name of your SQL service?', 'sql')); + $process = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'cd', $remoteProjectPath, '&&', 'docker-compose', 'ps', '-q', $sqlService]); + + try { + $process->mustRun(); + $containerIds = rtrim($process->getOutput(), PHP_EOL); + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + if (!empty($stackContext->dbms)) { + $dbms = $stackContext->dbms; + } else { + $dbms = strtolower($format->askQuestion(new ChoiceQuestion('Is it a MySQL or PostgreSQL database?', ['MySQL', 'PostgreSQL']))); + } + + $dumpName = $format->askQuestion(new Question('How do you want to name it?', 'dump.sql')); + $dumpPath = $remoteProjectPath.'/.docker/'.$dumpName; + $databaseName = $environementContext->database->databaseName; + $username = $environementContext->database->username; + $password = $environementContext->database->password; + + if ('postgresql' === $dbms) { + $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', '-i', $containerIds, 'pg_dump', '-U', $username, $databaseName, '>', $dumpPath]); + try { + $process2->mustRun(); + $format->success('Dump well created at '.$host->getUser().'@'.$host->getHostname().':'.$dumpPath); + + return 0; + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + } else { + $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', $containerIds, '/usr/bin/mysqldump', '-u', $username, '--password='.$password, $databaseName, '>', $dumpPath]); + try { + $process2->mustRun(); + if (!file_exists($host->getUser().'@'.$host->getHostname().':'.$dumpPath)) { + $format->error('Dump couldn\'t be created'); + + return 1; + } + $format->success('Dump well created at '.$host->getUser().'@'.$host->getHostname().':'.$dumpPath); + + return 0; + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + } + } +} diff --git a/src/Platform/Console/Command/Environment/DestroyCommand.php b/src/Platform/Console/Command/Environment/DestroyCommand.php index 4ea0488..2efdc24 100644 --- a/src/Platform/Console/Command/Environment/DestroyCommand.php +++ b/src/Platform/Console/Command/Environment/DestroyCommand.php @@ -104,10 +104,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $directories = explode('/', $workingDirectory); $projectName = end($directories); + $cd = 'cd '.$context->deployment->path; $commands = [ - 'docker:down' => 'cd '.$context->deployment->path.'/'.$projectName.' && docker-compose down -v', - 'directory:remove' => 'cd '.$context->deployment->path.' && rm -rf '.$projectName, + 'docker:down' => $cd.'/'.$projectName.' && docker-compose down -v', + 'directory:remove' => $cd.' && rm -rf '.$projectName, ]; foreach ($commands as $key => $value) { diff --git a/src/Platform/Console/Command/Environment/InitCommand.php b/src/Platform/Console/Command/Environment/InitCommand.php index fc7a77e..e0534b2 100644 --- a/src/Platform/Console/Command/Environment/InitCommand.php +++ b/src/Platform/Console/Command/Environment/InitCommand.php @@ -5,6 +5,7 @@ namespace Kiboko\Cloud\Platform\Console\Command\Environment; use Kiboko\Cloud\Domain\Environment\DTO\Context; +use Kiboko\Cloud\Domain\Environment\DTO\Database; use Kiboko\Cloud\Domain\Environment\DTO\Deployment; use Kiboko\Cloud\Domain\Environment\DTO\DirectValueEnvironmentVariable; use Kiboko\Cloud\Domain\Environment\DTO\SecretValueEnvironmentVariable; @@ -83,6 +84,11 @@ protected function execute(InputInterface $input, OutputInterface $output) ), $format->askQuestion(new Question('Please provide the path to your remote environment')), ), + new Database( + $format->askQuestion(new Question('Please provide the name of your database')), + $format->askQuestion(new Question('Please provide the user\'s name of your database')), + $format->askQuestion(new Question('Please provide the user\'s password of your database')), + ), ); $envDistPath = getcwd().'/.env.dist'; diff --git a/src/Platform/Console/Command/Environment/Variable/AddCommand.php b/src/Platform/Console/Command/Environment/Variable/AddCommand.php new file mode 100644 index 0000000..e418f29 --- /dev/null +++ b/src/Platform/Console/Command/Environment/Variable/AddCommand.php @@ -0,0 +1,109 @@ +wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Add an environment variable'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $variableName = $format->askQuestion(new Question('Please enter a variable name')); + $variableValue = $format->askQuestion(new Question('Please enter '.$variableName.' value')); + + $isSecret = false; + if ($variableValue) { + $isSecret = $format->askQuestion(new ConfirmationQuestion('Is this a secret variable ?', false)); + } + + if ($isSecret) { + $context->addVariable(new SecretValueEnvironmentVariable(new Variable($variableName), $variableValue)); + } else { + $context->addVariable(new DirectValueEnvironmentVariable(new Variable($variableName), $variableValue)); + } + + $format->note('Writing a new .kloud.environment.yaml file.'); + file_put_contents($workingDirectory.'/.kloud.environment.yaml', $serializer->serialize($context, 'yaml', [ + 'yaml_inline' => 4, + 'yaml_indent' => 0, + 'yaml_flags' => 0, + ])); + + return 0; + } +} From 3b874f2be0cfdd008bc3e6bf5dc518cedeb738ac Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 10:34:46 +0100 Subject: [PATCH 16/23] #12 : Remove useless code --- .../Environment/Database/DumpCommand.php | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/Platform/Console/Command/Environment/Database/DumpCommand.php b/src/Platform/Console/Command/Environment/Database/DumpCommand.php index 96fa1a8..5a8f8f0 100644 --- a/src/Platform/Console/Command/Environment/Database/DumpCommand.php +++ b/src/Platform/Console/Command/Environment/Database/DumpCommand.php @@ -4,12 +4,7 @@ namespace Kiboko\Cloud\Platform\Console\Command\Environment\Database; -use Deployer\Console\Application; -use Deployer\Deployer; use Deployer\Host\Host; -use Deployer\Logger\Handler\FileHandler; -use Deployer\Logger\Handler\NullHandler; -use Deployer\Logger\Logger; use Kiboko\Cloud\Domain\Environment\DTO\Context as EnvironmentContext; use Kiboko\Cloud\Domain\Stack\DTO\Context as StackContext; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; @@ -100,18 +95,6 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $application = new Application($this->console->getName()); - $deployer = new Deployer($application); - $deployer['output'] = $output; - $deployer['log_handler'] = function ($deployer) { - return !empty($deployer->config['log_file']) - ? new FileHandler($deployer->config['log_file']) - : new NullHandler(); - }; - $deployer['logger'] = function ($deployer) { - return new Logger($deployer['log_handler']); - }; - $host = new Host($environementContext->deployment->server->hostname); $host->port($environementContext->deployment->server->port); $host->user($environementContext->deployment->server->username); @@ -160,11 +143,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', $containerIds, '/usr/bin/mysqldump', '-u', $username, '--password='.$password, $databaseName, '>', $dumpPath]); try { $process2->mustRun(); - if (!file_exists($host->getUser().'@'.$host->getHostname().':'.$dumpPath)) { - $format->error('Dump couldn\'t be created'); - - return 1; - } $format->success('Dump well created at '.$host->getUser().'@'.$host->getHostname().':'.$dumpPath); return 0; From e70ba8bf90404166ab17ad2dd05f9b29aa221f45 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 11:46:24 +0100 Subject: [PATCH 17/23] #12 : Add database:load cmd and timeout(0) to database:dump cmd --- bin/kloud | 5 + .../Environment/Database/DumpCommand.php | 5 +- .../Environment/Database/LoadCommand.php | 155 ++++++++++++++++++ 3 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/Platform/Console/Command/Environment/Database/LoadCommand.php diff --git a/bin/kloud b/bin/kloud index 67aa541..df8d656 100755 --- a/bin/kloud +++ b/bin/kloud @@ -118,6 +118,11 @@ $app->addCommands([ Command\Environment\Database\DumpCommand::$defaultName, $app, )), + + (new Command\Environment\Database\LoadCommand( + Command\Environment\Database\LoadCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/Database/DumpCommand.php b/src/Platform/Console/Command/Environment/Database/DumpCommand.php index 5a8f8f0..4747008 100644 --- a/src/Platform/Console/Command/Environment/Database/DumpCommand.php +++ b/src/Platform/Console/Command/Environment/Database/DumpCommand.php @@ -105,7 +105,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $sqlService = $format->askQuestion(new Question('What is the name of your SQL service?', 'sql')); $process = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'cd', $remoteProjectPath, '&&', 'docker-compose', 'ps', '-q', $sqlService]); - try { $process->mustRun(); $containerIds = rtrim($process->getOutput(), PHP_EOL); @@ -130,7 +129,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if ('postgresql' === $dbms) { $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', '-i', $containerIds, 'pg_dump', '-U', $username, $databaseName, '>', $dumpPath]); try { - $process2->mustRun(); + $process2->setTimeout(0)->mustRun(); $format->success('Dump well created at '.$host->getUser().'@'.$host->getHostname().':'.$dumpPath); return 0; @@ -142,7 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } else { $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', $containerIds, '/usr/bin/mysqldump', '-u', $username, '--password='.$password, $databaseName, '>', $dumpPath]); try { - $process2->mustRun(); + $process2->setTimeout(0)->mustRun(); $format->success('Dump well created at '.$host->getUser().'@'.$host->getHostname().':'.$dumpPath); return 0; diff --git a/src/Platform/Console/Command/Environment/Database/LoadCommand.php b/src/Platform/Console/Command/Environment/Database/LoadCommand.php new file mode 100644 index 0000000..b6b89e8 --- /dev/null +++ b/src/Platform/Console/Command/Environment/Database/LoadCommand.php @@ -0,0 +1,155 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Dumps the database in the current state'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $environmentFile) { + try { + /** @var EnvironmentContext $environementContext */ + $environementContext = $serializer->deserialize($environmentFile->getContents(), EnvironmentContext::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + foreach ($finder->name('/^\.?kloud.ya?ml$/') as $stackFile) { + try { + /** @var StackContext $stackContext */ + $stackContext = $serializer->deserialize($stackFile->getContents(), StackContext::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($environementContext)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $host = new Host($environementContext->deployment->server->hostname); + $host->port($environementContext->deployment->server->port); + $host->user($environementContext->deployment->server->username); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + $remoteProjectPath = $environementContext->deployment->path.'/'.$projectName; + + $sqlService = $format->askQuestion(new Question('What is the name of your SQL service?', 'sql')); + $process = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'cd', $remoteProjectPath, '&&', 'docker-compose', 'ps', '-q', $sqlService]); + try { + $process->mustRun(); + $containerIds = rtrim($process->getOutput(), PHP_EOL); + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + if (!empty($stackContext->dbms)) { + $dbms = $stackContext->dbms; + } else { + $dbms = strtolower($format->askQuestion(new ChoiceQuestion('Is it a MySQL or PostgreSQL database?', ['MySQL', 'PostgreSQL']))); + } + + $dumpName = $format->askQuestion(new Question('Name of your SQL dump to load')); + $dumpPath = $remoteProjectPath.'/.docker/'.$dumpName; + $databaseName = $environementContext->database->databaseName; + $username = $environementContext->database->username; + $password = $environementContext->database->password; + + if ('postgresql' === $dbms) { + $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', '-i', $containerIds, 'psql', '-U', $username, $databaseName, '<', $dumpPath]); + try { + $process2->setTimeout(0)->mustRun(); + $format->success('Dump well loaded'); + + return 0; + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + } else { + $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'docker', 'exec', '-i', $containerIds, 'mysql', '-u', $username, '--password='.$password, $databaseName, '<', $dumpPath]); + try { + $process2->setTimeout(0)->mustRun(); + $format->success('Dump well loaded'); + + return 0; + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + } + } +} From 4bc7a608461969f722fff0bfc9c1c48a4cd6abe3 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 16:10:29 +0100 Subject: [PATCH 18/23] #12 : Add shell cmd --- bin/kloud | 21 ++-- .../Environment/Database/DumpCommand.php | 3 +- .../Environment/Database/LoadCommand.php | 3 +- .../Command/Environment/ShellCommand.php | 116 ++++++++++++++++++ .../Command/Environment/StopCommand.php | 2 +- 5 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 src/Platform/Console/Command/Environment/ShellCommand.php diff --git a/bin/kloud b/bin/kloud index df8d656..8dd1b6b 100755 --- a/bin/kloud +++ b/bin/kloud @@ -64,8 +64,12 @@ $app->addCommands([ Command\Environment\InitCommand::$defaultName, )), - (new Command\Environment\Variable\ListCommand( - Command\Environment\Variable\ListCommand::$defaultName, + (new Command\Environment\Variable\AddCommand( + Command\Environment\Variable\AddCommand::$defaultName, + )), + + (new Command\Environment\Variable\UnsetCommand( + Command\Environment\Variable\UnsetCommand::$defaultName, )), (new Command\Environment\Variable\GetCommand( @@ -76,12 +80,8 @@ $app->addCommands([ Command\Environment\Variable\SetCommand::$defaultName, )), - (new Command\Environment\Variable\AddCommand( - Command\Environment\Variable\AddCommand::$defaultName, - )), - - (new Command\Environment\Variable\UnsetCommand( - Command\Environment\Variable\UnsetCommand::$defaultName, + (new Command\Environment\Variable\ListCommand( + Command\Environment\Variable\ListCommand::$defaultName, )), (new Command\Environment\DeployCommand( @@ -123,6 +123,11 @@ $app->addCommands([ Command\Environment\Database\LoadCommand::$defaultName, $app, )), + + (new Command\Environment\ShellCommand( + Command\Environment\ShellCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/Database/DumpCommand.php b/src/Platform/Console/Command/Environment/Database/DumpCommand.php index 4747008..7bce4d6 100644 --- a/src/Platform/Console/Command/Environment/Database/DumpCommand.php +++ b/src/Platform/Console/Command/Environment/Database/DumpCommand.php @@ -25,7 +25,6 @@ final class DumpCommand extends Command { public static $defaultName = 'environment:database:dump'; - private Console $console; private EnvironmentWizard $wizard; @@ -38,7 +37,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Dumps the database in the current state'); + $this->setDescription('Dump the database in the current state'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/Database/LoadCommand.php b/src/Platform/Console/Command/Environment/Database/LoadCommand.php index b6b89e8..ca37149 100644 --- a/src/Platform/Console/Command/Environment/Database/LoadCommand.php +++ b/src/Platform/Console/Command/Environment/Database/LoadCommand.php @@ -25,7 +25,6 @@ final class LoadCommand extends Command { public static $defaultName = 'environment:database:load'; - private Console $console; private EnvironmentWizard $wizard; @@ -38,7 +37,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Dumps the database in the current state'); + $this->setDescription('Load a database dump'); $this->wizard->configureConsoleCommand($this); } diff --git a/src/Platform/Console/Command/Environment/ShellCommand.php b/src/Platform/Console/Command/Environment/ShellCommand.php new file mode 100644 index 0000000..9df1656 --- /dev/null +++ b/src/Platform/Console/Command/Environment/ShellCommand.php @@ -0,0 +1,116 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Start a shell session for a service'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + + $directories = explode('/', $workingDirectory); + $projectName = end($directories); + $remoteProjectPath = $context->deployment->path.'/'.$projectName; + + $service = $format->askQuestion(new Question('For what service you want to start a shell session?')); + $process = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'cd', $remoteProjectPath, '&&', 'docker-compose', 'ps', '-q', $service]); + try { + $process->mustRun(); + $containerIds = rtrim($process->getOutput(), PHP_EOL); + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + $process2 = new Process(['ssh', '-t', $host->getUser().'@'.$host->getHostname(), 'cd', $remoteProjectPath, '&&', 'docker', 'exec', '-ti', $containerIds, 'sh']); + try { + $process2->setTty(Process::isTtySupported())->setTimeout(0)->mustRun(); + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + return 0; + } +} diff --git a/src/Platform/Console/Command/Environment/StopCommand.php b/src/Platform/Console/Command/Environment/StopCommand.php index 3164bed..c4684a7 100644 --- a/src/Platform/Console/Command/Environment/StopCommand.php +++ b/src/Platform/Console/Command/Environment/StopCommand.php @@ -42,7 +42,7 @@ public function __construct(?string $name, Console $console) protected function configure() { - $this->setDescription('Stoptty docker services on the remote server'); + $this->setDescription('Stop docker services on the remote server'); $this->wizard->configureConsoleCommand($this); } From f1db0464e625223f9b75b9e5a5db641152fe2031 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Fri, 20 Nov 2020 16:10:51 +0100 Subject: [PATCH 19/23] #12 : Add doc to README.md --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05ea56a..eaed6a2 100644 --- a/README.md +++ b/README.md @@ -112,13 +112,33 @@ If you need to build the images you need in a stack, you can execute the followi `kloud image:build` To enable experimental images, you will need to add option `--with-experimental`. - + ### Test the required images If you need to test if the images you are using are following every constraint you would expect: `kloud image:test` +### Environment commands + +After `kloud stack:init`, you can use several environment commands to do several things on a remote server + +* `kloud environment:init`: Initialize the environment file in local workspace. +* `kloud environment:variable:add`: Add an environment variable. +* `kloud environment:variable:unset`: Unset an environment variable value. +* `kloud environment:variable:get`: Print an environment variable value. +* `kloud environment:variable:set`: Change an environment variable value. +* `kloud environment:variable:list`: Print the list of environment variables and their value. +* `kloud environment:deploy`: Deploy the application to a remote server using rsync and initialize docker services. +* `kloud environment:destroy`: Destroy the docker infrastructure with associated volumes and remove remote directory. +* `kloud environment:start`: Start docker services on the remote server. +* `kloud environment:stop`: Stop docker services on the remote server. +* `kloud environment:rsync`: Synchronize remote directory according to local directory. +* `kloud environment:cache:clear`: SClear cache and restart FPM service. +* `kloud environment:database:dump`: Dump the database in the current state. +* `kloud environment:database:load`: Load a database dump. +* `kloud environment:shell`: Start a shell session for a service. + Frequently Asked Questions --- From 5fc566c7551aad196f112a60f9aec0e4a5363e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Wed, 25 Nov 2020 17:35:46 +0100 Subject: [PATCH 20/23] Updated retrieved lost docs about environment commands --- docs/usage.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 1d9b17f..67977ee 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -63,3 +63,24 @@ Test the required images If you need to test if the images you are using are following every constraint you would expect: `kloud image:test` + +Environment commands +--- + +After `kloud stack:init`, you can use several environment commands to do several things on a remote server + +* `kloud environment:init`: Initialize the environment file in local workspace. +* `kloud environment:variable:add`: Add an environment variable. +* `kloud environment:variable:unset`: Unset an environment variable value. +* `kloud environment:variable:get`: Print an environment variable value. +* `kloud environment:variable:set`: Change an environment variable value. +* `kloud environment:variable:list`: Print the list of environment variables and their value. +* `kloud environment:deploy`: Deploy the application to a remote server using rsync and initialize docker services. +* `kloud environment:destroy`: Destroy the docker infrastructure with associated volumes and remove remote directory. +* `kloud environment:start`: Start docker services on the remote server. +* `kloud environment:stop`: Stop docker services on the remote server. +* `kloud environment:rsync`: Synchronize remote directory according to local directory. +* `kloud environment:cache:clear`: SClear cache and restart FPM service. +* `kloud environment:database:dump`: Dump the database in the current state. +* `kloud environment:database:load`: Load a database dump. +* `kloud environment:shell`: Start a shell session for a service. From 4c1e9d901e1da956c803b0922447959c1970e827 Mon Sep 17 00:00:00 2001 From: nicolaschautard Date: Thu, 10 Dec 2020 15:32:42 +0100 Subject: [PATCH 21/23] #12 : Add proxy command for port forwarding --- bin/kloud | 5 + .../Command/Environment/ProxyCommand.php | 111 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/Platform/Console/Command/Environment/ProxyCommand.php diff --git a/bin/kloud b/bin/kloud index 8dd1b6b..317389c 100755 --- a/bin/kloud +++ b/bin/kloud @@ -128,6 +128,11 @@ $app->addCommands([ Command\Environment\ShellCommand::$defaultName, $app, )), + + (new Command\Environment\ProxyCommand( + Command\Environment\ProxyCommand::$defaultName, + $app, + )), ]); $app->run(new ArgvInput($argv), new ConsoleOutput()); diff --git a/src/Platform/Console/Command/Environment/ProxyCommand.php b/src/Platform/Console/Command/Environment/ProxyCommand.php new file mode 100644 index 0000000..2663e6f --- /dev/null +++ b/src/Platform/Console/Command/Environment/ProxyCommand.php @@ -0,0 +1,111 @@ +console = $console; + $this->wizard = new EnvironmentWizard(); + parent::__construct($name); + } + + protected function configure() + { + $this->setDescription('Port forwarding using ssh tunnel'); + + $this->wizard->configureConsoleCommand($this); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $workingDirectory = $input->getOption('working-directory') ?: getcwd(); + + $finder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory); + + $format = new SymfonyStyle($input, $output); + + $serializer = new Serializer( + [ + new CustomNormalizer(), + new PropertyNormalizer(), + ], + [ + new YamlEncoder(), + ] + ); + + if ($finder->hasResults()) { + /** @var SplFileInfo $file */ + foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $file) { + try { + /** @var Context $context */ + $context = $serializer->deserialize($file->getContents(), Context::class, 'yaml'); + } catch (\Throwable $exception) { + $format->error($exception->getMessage()); + continue; + } + + break; + } + } + + if (!isset($context)) { + $format->error('No .kloud.environment.yaml file found in your directory. You must initialize it using environment:init command'); + + return 1; + } + + $host = new Host($context->deployment->server->hostname); + $host->port($context->deployment->server->port); + $host->user($context->deployment->server->username); + + $type = $format->askQuestion(new ChoiceQuestion('What type of port forwarding is it?', ['Local to remote', 'Remote to local'])); + $localPort = $format->askQuestion(new Question('What is the local port you want to tunnel?')); + $remotePort = $format->askQuestion(new Question('What is the remote port you want to tunnel?')); + + if ('Local' === $type) { + $process = new Process(['ssh', '-L', $localPort.':'.$host->getHostname().':'.$remotePort, $host->getUser().'@'.$host->getHostname()]); + } else { + $process = new Process(['ssh', '-R', $remotePort.':127.0.0.1:'.$localPort, $host->getUser().'@'.$host->getHostname()]); + } + + try { + $process->setTty(Process::isTtySupported())->setTimeout(0)->mustRun(); + } catch (\Exception $exception) { + $format->error($exception->getMessage()); + + return 1; + } + + return 0; + } +} From 1684b03fd8987b5782b28a3dbf4178a80c83624f Mon Sep 17 00:00:00 2001 From: sebprt Date: Tue, 22 Dec 2020 16:48:06 +0100 Subject: [PATCH 22/23] Remove typo --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 67977ee..bdc7e1f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -80,7 +80,7 @@ After `kloud stack:init`, you can use several environment commands to do several * `kloud environment:start`: Start docker services on the remote server. * `kloud environment:stop`: Stop docker services on the remote server. * `kloud environment:rsync`: Synchronize remote directory according to local directory. -* `kloud environment:cache:clear`: SClear cache and restart FPM service. +* `kloud environment:cache:clear`: Clear cache and restart FPM service. * `kloud environment:database:dump`: Dump the database in the current state. * `kloud environment:database:load`: Load a database dump. * `kloud environment:shell`: Start a shell session for a service. From c556e8abd989a2643da49ad1bd405354f1775ad1 Mon Sep 17 00:00:00 2001 From: sebprt Date: Tue, 22 Dec 2020 16:53:52 +0100 Subject: [PATCH 23/23] Fix dump and load commands : Create Normalizer and a second Finder instance --- .../Environment/Database/DumpCommand.php | 19 ++++++++---- .../Environment/Database/LoadCommand.php | 19 ++++++++---- .../Normalizer/StackContextNormalizer.php | 29 +++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 src/Platform/Serializer/Normalizer/StackContextNormalizer.php diff --git a/src/Platform/Console/Command/Environment/Database/DumpCommand.php b/src/Platform/Console/Command/Environment/Database/DumpCommand.php index 7bce4d6..7f4b9bf 100644 --- a/src/Platform/Console/Command/Environment/Database/DumpCommand.php +++ b/src/Platform/Console/Command/Environment/Database/DumpCommand.php @@ -8,6 +8,7 @@ use Kiboko\Cloud\Domain\Environment\DTO\Context as EnvironmentContext; use Kiboko\Cloud\Domain\Stack\DTO\Context as StackContext; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; +use Kiboko\Cloud\Platform\Serializer\Normalizer\StackContextNormalizer; use Symfony\Component\Console\Application as Console; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -46,15 +47,23 @@ protected function execute(InputInterface $input, OutputInterface $output) { $workingDirectory = $input->getOption('working-directory') ?: getcwd(); - $finder = (new Finder()) + $environmentFinder = (new Finder()) ->files() ->ignoreDotFiles(false) - ->in($workingDirectory); + ->in($workingDirectory) + ->name('/^\.?kloud.environment.ya?ml$/'); + + $stackFinder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory) + ->name('/^\.?kloud.ya?ml$/'); $format = new SymfonyStyle($input, $output); $serializer = new Serializer( [ + new StackContextNormalizer(), new CustomNormalizer(), new PropertyNormalizer(), ], @@ -63,8 +72,8 @@ protected function execute(InputInterface $input, OutputInterface $output) ] ); - if ($finder->hasResults()) { - foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $environmentFile) { + if ($environmentFinder->hasResults()) { + foreach ($environmentFinder as $environmentFile) { try { /** @var EnvironmentContext $environementContext */ $environementContext = $serializer->deserialize($environmentFile->getContents(), EnvironmentContext::class, 'yaml'); @@ -75,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) break; } - foreach ($finder->name('/^\.?kloud.ya?ml$/') as $stackFile) { + foreach ($stackFinder as $stackFile) { try { /** @var StackContext $stackContext */ $stackContext = $serializer->deserialize($stackFile->getContents(), StackContext::class, 'yaml'); diff --git a/src/Platform/Console/Command/Environment/Database/LoadCommand.php b/src/Platform/Console/Command/Environment/Database/LoadCommand.php index ca37149..a2612c8 100644 --- a/src/Platform/Console/Command/Environment/Database/LoadCommand.php +++ b/src/Platform/Console/Command/Environment/Database/LoadCommand.php @@ -8,6 +8,7 @@ use Kiboko\Cloud\Domain\Environment\DTO\Context as EnvironmentContext; use Kiboko\Cloud\Domain\Stack\DTO\Context as StackContext; use Kiboko\Cloud\Platform\Console\EnvironmentWizard; +use Kiboko\Cloud\Platform\Serializer\Normalizer\StackContextNormalizer; use Symfony\Component\Console\Application as Console; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -46,15 +47,23 @@ protected function execute(InputInterface $input, OutputInterface $output) { $workingDirectory = $input->getOption('working-directory') ?: getcwd(); - $finder = (new Finder()) + $environmentFinder = (new Finder()) ->files() ->ignoreDotFiles(false) - ->in($workingDirectory); + ->in($workingDirectory) + ->name('/^\.?kloud.environment.ya?ml$/'); + + $stackFinder = (new Finder()) + ->files() + ->ignoreDotFiles(false) + ->in($workingDirectory) + ->name('/^\.?kloud.ya?ml$/'); $format = new SymfonyStyle($input, $output); $serializer = new Serializer( [ + new StackContextNormalizer(), new CustomNormalizer(), new PropertyNormalizer(), ], @@ -63,8 +72,8 @@ protected function execute(InputInterface $input, OutputInterface $output) ] ); - if ($finder->hasResults()) { - foreach ($finder->name('/^\.?kloud.environment.ya?ml$/') as $environmentFile) { + if ($environmentFinder->hasResults()) { + foreach ($environmentFinder->name('/^\.?kloud.environment.ya?ml$/') as $environmentFile) { try { /** @var EnvironmentContext $environementContext */ $environementContext = $serializer->deserialize($environmentFile->getContents(), EnvironmentContext::class, 'yaml'); @@ -75,7 +84,7 @@ protected function execute(InputInterface $input, OutputInterface $output) break; } - foreach ($finder->name('/^\.?kloud.ya?ml$/') as $stackFile) { + foreach ($stackFinder->name('/^\.?kloud.ya?ml$/') as $stackFile) { try { /** @var StackContext $stackContext */ $stackContext = $serializer->deserialize($stackFile->getContents(), StackContext::class, 'yaml'); diff --git a/src/Platform/Serializer/Normalizer/StackContextNormalizer.php b/src/Platform/Serializer/Normalizer/StackContextNormalizer.php new file mode 100644 index 0000000..c763518 --- /dev/null +++ b/src/Platform/Serializer/Normalizer/StackContextNormalizer.php @@ -0,0 +1,29 @@ +