From d5d6f55acd0fb432c2bb59711f557f8c7a460e4e Mon Sep 17 00:00:00 2001 From: Peter Jaap Blaakmeer Date: Wed, 28 Sep 2022 20:50:27 +0200 Subject: [PATCH 1/4] Added support and readme for environment variables --- Model/Processor/ImportProcessor.php | 24 +++++++++++- .../Resolver/EnvironmentVariableResolver.php | 39 +++++++++++++++++++ docs/config-import.md | 14 +++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Model/Resolver/EnvironmentVariableResolver.php diff --git a/Model/Processor/ImportProcessor.php b/Model/Processor/ImportProcessor.php index 0dd2d4f..78fcf8a 100644 --- a/Model/Processor/ImportProcessor.php +++ b/Model/Processor/ImportProcessor.php @@ -10,6 +10,7 @@ use Semaio\ConfigImportExport\Model\Converter\ScopeConverterInterface; use Semaio\ConfigImportExport\Model\File\FinderInterface; use Semaio\ConfigImportExport\Model\File\Reader\ReaderInterface; +use Semaio\ConfigImportExport\Model\Resolver\EnvironmentVariableResolver; use Semaio\ConfigImportExport\Model\Validator\ScopeValidatorInterface; class ImportProcessor extends AbstractProcessor implements ImportProcessorInterface @@ -39,19 +40,27 @@ class ImportProcessor extends AbstractProcessor implements ImportProcessorInterf */ private $scopeConverter; + /** + * @var EnvironmentVariableResolver + */ + private $environmentVariableResolver; + /** * @param WriterInterface $configWriter * @param ScopeValidatorInterface $scopeValidator * @param ScopeConverterInterface $scopeConverter + * @param EnvironmentVariableResolver $environmentVariableResolver */ public function __construct( WriterInterface $configWriter, ScopeValidatorInterface $scopeValidator, - ScopeConverterInterface $scopeConverter + ScopeConverterInterface $scopeConverter, + EnvironmentVariableResolver $environmentVariableResolver ) { $this->configWriter = $configWriter; $this->scopeValidator = $scopeValidator; $this->scopeConverter = $scopeConverter; + $this->environmentVariableResolver = $environmentVariableResolver; } /** @@ -145,6 +154,19 @@ public function transformConfigToScopeConfig($path, array $config) continue; } + try { + $value = $this->environmentVariableResolver->resolveValue($value); + } catch (\UnexpectedValueException $e) { + $errorMsg = sprintf( + '%s (%s => %s)', + $e->getMessage(), + $path, + $value + ); + $this->getOutput()->writeln($errorMsg); + continue; + } + $return[] = [ 'value' => $value, 'scope' => $scope, diff --git a/Model/Resolver/EnvironmentVariableResolver.php b/Model/Resolver/EnvironmentVariableResolver.php new file mode 100644 index 0000000..882b38a --- /dev/null +++ b/Model/Resolver/EnvironmentVariableResolver.php @@ -0,0 +1,39 @@ +value = $value; + return preg_replace_callback( + '/\{env:([^:\}\{]+?)\}/', + function ($matches) { + $resolvedValue = getenv($matches[1]); + if ($resolvedValue === false) { + throw new \UnexpectedValueException(sprintf('Environment variable %s does not exist', $this->value)); + } + return $resolvedValue; + }, + $value + ); + } +} diff --git a/docs/config-import.md b/docs/config-import.md index e714c72..a87541b 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -56,6 +56,20 @@ To import the Magento configuration settings for ([@therouv](https://github.com/ php bin/magento config:data:import config/store dev/therouv ``` +### Environment Variables substitution + +If you do not want to store your secrets in version control, you can use placeholders for environment variables in the configuration files. This is done with the notation `{env:ENV_VAR_NAME}`. + +For example, this might be the content of your config file: + +``` +vendorx/general/api_key: + default: + 0: {env:VENDORX_API_KEY} +``` + +You can then set the environment variable `VENDORX_API_KEY` in your CI/CD configuration to the secret API key. + ### Recursive folder setup If you choose to store your configuration files in subdirectories, e.g. per vendor, the recommended folder setup should look like this: From b6e2a8f27b95ccf864fe3e0ec28e46b8ee170a03 Mon Sep 17 00:00:00 2001 From: Peter Jaap Blaakmeer Date: Wed, 28 Sep 2022 20:51:17 +0200 Subject: [PATCH 2/4] Add License header --- Model/Resolver/EnvironmentVariableResolver.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Model/Resolver/EnvironmentVariableResolver.php b/Model/Resolver/EnvironmentVariableResolver.php index 882b38a..24a2c78 100644 --- a/Model/Resolver/EnvironmentVariableResolver.php +++ b/Model/Resolver/EnvironmentVariableResolver.php @@ -1,6 +1,9 @@ Date: Thu, 29 Sep 2022 09:38:20 +0200 Subject: [PATCH 3/4] Changed syntax to match Symfony env var, added unit tests --- .../Resolver/EnvironmentVariableResolver.php | 4 +- .../EnvironmentVariableResolverTest.php | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 Test/Unit/Model/Resolver/EnvironmentVariableResolverTest.php diff --git a/Model/Resolver/EnvironmentVariableResolver.php b/Model/Resolver/EnvironmentVariableResolver.php index 24a2c78..0eb97f2 100644 --- a/Model/Resolver/EnvironmentVariableResolver.php +++ b/Model/Resolver/EnvironmentVariableResolver.php @@ -28,11 +28,11 @@ public function resolveValue($value) { $this->value = $value; return preg_replace_callback( - '/\{env:([^:\}\{]+?)\}/', + '/\%env\(([^:\%\%]+?)\)\%/', function ($matches) { $resolvedValue = getenv($matches[1]); if ($resolvedValue === false) { - throw new \UnexpectedValueException(sprintf('Environment variable %s does not exist', $this->value)); + throw new \UnexpectedValueException(sprintf('Environment variable %s does not exist', $matches[1])); } return $resolvedValue; }, diff --git a/Test/Unit/Model/Resolver/EnvironmentVariableResolverTest.php b/Test/Unit/Model/Resolver/EnvironmentVariableResolverTest.php new file mode 100644 index 0000000..486d15c --- /dev/null +++ b/Test/Unit/Model/Resolver/EnvironmentVariableResolverTest.php @@ -0,0 +1,52 @@ +environmentVariableResolver = new EnvironmentVariableResolver(); + } + + /** + * @test + */ + public function validate(): void + { + $this->assertEquals($this->environmentVariableResolver->resolveValue('test_without_env_var'), 'test_without_env_var'); + + $this->assertEquals($this->environmentVariableResolver->resolveValue('%env(HOSTNAME)%'), 'testvalue1'); + $this->assertEquals($this->environmentVariableResolver->resolveValue('https://%env(SUBDOMAIN)%.example.com'), 'https://testvalue2.example.com'); + $this->assertEquals($this->environmentVariableResolver->resolveValue('%env(CONCAT_THIS)%%env(WITH_THIS)%'), 'testvalue3testvalue4'); + + $this->expectException(\UnexpectedValueException::class); + $this->environmentVariableResolver->resolveValue('%env(DOESNOTEXIST)%'); + } + +} From 2207fb65e680c58ac0b7e7c1958ce06f75f6486a Mon Sep 17 00:00:00 2001 From: Peter Jaap Blaakmeer Date: Thu, 29 Sep 2022 09:54:12 +0200 Subject: [PATCH 4/4] updated readme to match Symfony env var syntax --- docs/config-import.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config-import.md b/docs/config-import.md index a87541b..7e12886 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -58,14 +58,14 @@ php bin/magento config:data:import config/store dev/therouv ### Environment Variables substitution -If you do not want to store your secrets in version control, you can use placeholders for environment variables in the configuration files. This is done with the notation `{env:ENV_VAR_NAME}`. +If you do not want to store your secrets in version control, you can use placeholders for environment variables in the configuration files. This is done with the notation `%env(ENV_VAR_NAME)%`. For example, this might be the content of your config file: ``` vendorx/general/api_key: default: - 0: {env:VENDORX_API_KEY} + 0: %env(VENDORX_API_KEY)% ``` You can then set the environment variable `VENDORX_API_KEY` in your CI/CD configuration to the secret API key.