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..0eb97f2 --- /dev/null +++ b/Model/Resolver/EnvironmentVariableResolver.php @@ -0,0 +1,42 @@ +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', $matches[1])); + } + return $resolvedValue; + }, + $value + ); + } +} 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)%'); + } + +} diff --git a/docs/config-import.md b/docs/config-import.md index 33a7d91..1ea0b83 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -58,6 +58,20 @@ php bin/magento config:data:import config/store dev/therouv The files in the `base` folder will always be imported (if they exist), regardless of which environment parameter has been passed. If the base and environment configurations have the same configuration field set, then the environment value for that configuration will overwrite the base configuration. +### 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: