From c34dc29cb96a1872a154cb027c223708c7738527 Mon Sep 17 00:00:00 2001 From: Vlad Podorozhnyi Date: Tue, 19 Mar 2024 14:27:47 +0100 Subject: [PATCH 1/5] Allow to KEEP configs unchanged on the environemnt. So base config will not override configs that are already set on environment. --- Model/Processor/ImportProcessor.php | 66 +++++++++++++------ .../Model/Processor/ImportProcessorTest.php | 16 ++++- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/Model/Processor/ImportProcessor.php b/Model/Processor/ImportProcessor.php index 470b643..fe6ff6c 100644 --- a/Model/Processor/ImportProcessor.php +++ b/Model/Processor/ImportProcessor.php @@ -19,6 +19,8 @@ class ImportProcessor extends AbstractProcessor implements ImportProcessorInterface { private const DELETE_CONFIG_FLAG = '!!DELETE'; + private const KEEP_CONFIG_FLAG = '!!KEEP'; + /** * @var WriterInterface */ @@ -92,39 +94,63 @@ public function process() $this->getOutput()->writeln('Maybe this is expected behaviour, because you passed the --allow-empty-directories option.'); } + $configs = $this->collectConfigs($files); + + foreach ($configs as $configPath => $configValue) { + foreach ($configValue as $scopeType => $scopeValue) { + foreach ($scopeValue as $scopeId => $value) { + + if ($value === self::DELETE_CONFIG_FLAG) { + $this->configWriter->delete($configPath, $scopeType, $scopeId); + $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'DELETED')); + continue; + } + + if ($value === self::KEEP_CONFIG_FLAG) { + $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'KEPT')); + continue; + } + + $this->configWriter->save($configPath, $value, $scopeType, $scopeId); + $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, $value)); + } + } + } + } + + /** + * @param array $files + * @return array + */ + private function collectConfigs(array $files): array + { + $buffer = []; + foreach ($files as $file) { $valuesSet = 0; $configurations = $this->getConfigurationsFromFile($file); + foreach ($configurations as $configPath => $configValues) { - $scopeConfigValues = $this->transformConfigToScopeConfig($configPath, $configValues); - foreach ($scopeConfigValues as $scopeConfigValue) { - if ($scopeConfigValue['value'] === self::DELETE_CONFIG_FLAG) { - $this->configWriter->delete( - $configPath, - $scopeConfigValue['scope'], - $this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope']) - ); - $this->getOutput()->writeln(sprintf('%s => %s', $configPath, 'DELETED')); - $valuesSet++; + if (!isset($buffer[$configPath])) { + $buffer[$configPath] = []; + } - continue; - } + $scopeConfigValues = $this->transformConfigToScopeConfig($configPath, $configValues); - $this->configWriter->save( - $configPath, - $scopeConfigValue['value'], - $scopeConfigValue['scope'], - $this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope']) - ); + foreach ($scopeConfigValues as $scopeConfigValue) { - $this->getOutput()->writeln(sprintf('%s => %s', $configPath, $scopeConfigValue['value'])); + $scopeType = $scopeConfigValue['scope']; + $scopeId = $this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope']); + $buffer[$configPath][$scopeType][$scopeId] = $scopeConfigValue['value']; $valuesSet++; } } - $this->getOutput()->writeln(sprintf('Processed: %s with %s value(s).', $file, $valuesSet)); + $this->getOutput()->writeln(sprintf('Configs collected: %s with %s value(s).', $file, $valuesSet)); } + + return $buffer; } /** diff --git a/Test/Unit/Model/Processor/ImportProcessorTest.php b/Test/Unit/Model/Processor/ImportProcessorTest.php index e1be178..c54362a 100644 --- a/Test/Unit/Model/Processor/ImportProcessorTest.php +++ b/Test/Unit/Model/Processor/ImportProcessorTest.php @@ -14,6 +14,7 @@ use Semaio\ConfigImportExport\Model\File\Reader\YamlReader; use Semaio\ConfigImportExport\Model\Processor\ImportProcessor; use Semaio\ConfigImportExport\Model\Validator\ScopeValidatorInterface; +use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class ImportProcessorTest extends TestCase @@ -67,6 +68,9 @@ public function processWithoutFiles(): void $this->expectException(InvalidArgumentException::class); $processor = new ImportProcessor($this->configWriterMock, $this->scopeValidatorMock, $this->scopeConverterMock, []); + $inputMock = $this->getMockBuilder(InputInterface::class)->getMock(); + $inputMock->method('getOption')->with('allow-empty-directories')->willReturn(false); + $processor->setInput($inputMock); $processor->setFinder($finderMock); $processor->process(); } @@ -126,6 +130,16 @@ public function process(): void 0 => '!!DELETE', ], ], + 'test/config/custom_field_to_be_keeped' => [ + 'default' => [ + 0 => 'VALUE_THAT_SHOULD_NOT_BE_PROCESSED', + ], + ], + 'test/config/custom_field_to_be_keeped' => [ + 'default' => [ + 0 => '!!KEEP', + ], + ], ]; $readerMock = $this->getMockBuilder(YamlReader::class) @@ -133,7 +147,7 @@ public function process(): void ->getMock(); $readerMock->expects($this->once())->method('parse')->willReturn($parseResult); - $this->scopeValidatorMock->expects($this->exactly(2))->method('validate')->willReturn(true); + $this->scopeValidatorMock->expects($this->exactly(3))->method('validate')->willReturn(true); $this->configWriterMock->expects($this->once())->method('save'); $this->configWriterMock->expects($this->once())->method('delete'); From 72ad40c761ea23271de3e1bcca9ec3c51324d3da Mon Sep 17 00:00:00 2001 From: Vlad Podorozhnyi Date: Tue, 19 Mar 2024 20:03:29 +0100 Subject: [PATCH 2/5] Fix phpcs standards. --- Model/Processor/ImportProcessor.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Model/Processor/ImportProcessor.php b/Model/Processor/ImportProcessor.php index fe6ff6c..ecc8b73 100644 --- a/Model/Processor/ImportProcessor.php +++ b/Model/Processor/ImportProcessor.php @@ -103,11 +103,13 @@ public function process() if ($value === self::DELETE_CONFIG_FLAG) { $this->configWriter->delete($configPath, $scopeType, $scopeId); $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'DELETED')); + continue; } if ($value === self::KEEP_CONFIG_FLAG) { $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'KEPT')); + continue; } @@ -120,6 +122,7 @@ public function process() /** * @param array $files + * * @return array */ private function collectConfigs(array $files): array From a779440c797cb1953a1157dd5423e2b8c5b17e27 Mon Sep 17 00:00:00 2001 From: Vlad Podorozhnyi Date: Tue, 19 Mar 2024 20:12:55 +0100 Subject: [PATCH 3/5] Add KEEP flag documentation. --- docs/config-import.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/config-import.md b/docs/config-import.md index 05bfd53..202eea5 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -97,6 +97,21 @@ vendorx/general/api_key: 0: "!!DELETE" ``` +### Keep Config + +If you want to make sure that config value will not be affected by config importer - use `!!KEEP` magic-ish string. + +#### When would it be helpful? +When you have a bunch of same configs in different environments, but there one environment ( `X` env ) where you want to keep it as is, but not dispose the exact value of `X` env in config files. +Security sensitive data are good examples of such cases - you want them to be kept only in env DB, and definitely not in your GIT repo. + +```yaml +vendorx/general/api_key: + default: + 0: "!!KEEP" +``` + + ### 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 a1d94fe0f8e7f1206ce1cf4bf2aa13228b944c0d Mon Sep 17 00:00:00 2001 From: Rouven Alexander Rieker Date: Thu, 18 Apr 2024 10:40:59 +0200 Subject: [PATCH 4/5] chore: refactor code to be more clear --- Model/Processor/ImportProcessor.php | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/Model/Processor/ImportProcessor.php b/Model/Processor/ImportProcessor.php index ecc8b73..56ac276 100644 --- a/Model/Processor/ImportProcessor.php +++ b/Model/Processor/ImportProcessor.php @@ -87,19 +87,25 @@ public function __construct( public function process() { $files = $this->finder->find(); - if (0 === count($files) && false === $this->getInput()->getOption('allow-empty-directories')) { - throw new \InvalidArgumentException('No files found for format: *.' . $this->getFormat()); - } else { - $this->getOutput()->writeln('No files found for format: *.' . $this->getFormat()); - $this->getOutput()->writeln('Maybe this is expected behaviour, because you passed the --allow-empty-directories option.'); + + if (0 === count($files)) { + if (false === $this->getInput()->getOption('allow-empty-directories')) { + throw new \InvalidArgumentException('No files found for format: *.' . $this->getFormat()); + } else { + $this->getOutput()->writeln('No files found for format: *.' . $this->getFormat()); + $this->getOutput()->writeln('Maybe this is expected behaviour, because you passed the --allow-empty-directories option.'); + $this->getOutput()->writeln(' '); + } } - $configs = $this->collectConfigs($files); + $configurationValues = $this->collectConfigurationValues($files); + if (0 === count($configurationValues)) { + return; + } - foreach ($configs as $configPath => $configValue) { + foreach ($configurationValues as $configPath => $configValue) { foreach ($configValue as $scopeType => $scopeValue) { foreach ($scopeValue as $scopeId => $value) { - if ($value === self::DELETE_CONFIG_FLAG) { $this->configWriter->delete($configPath, $scopeType, $scopeId); $this->getOutput()->writeln(sprintf('[%s] [%s] %s => %s', $scopeType, $scopeId, $configPath, 'DELETED')); @@ -125,24 +131,21 @@ public function process() * * @return array */ - private function collectConfigs(array $files): array + private function collectConfigurationValues(array $files): array { $buffer = []; foreach ($files as $file) { $valuesSet = 0; - $configurations = $this->getConfigurationsFromFile($file); + $configurations = $this->getConfigurationsFromFile($file); foreach ($configurations as $configPath => $configValues) { - if (!isset($buffer[$configPath])) { $buffer[$configPath] = []; } $scopeConfigValues = $this->transformConfigToScopeConfig($configPath, $configValues); - foreach ($scopeConfigValues as $scopeConfigValue) { - $scopeType = $scopeConfigValue['scope']; $scopeId = $this->scopeConverter->convert($scopeConfigValue['scope_id'], $scopeConfigValue['scope']); $buffer[$configPath][$scopeType][$scopeId] = $scopeConfigValue['value']; @@ -150,9 +153,20 @@ private function collectConfigs(array $files): array } } - $this->getOutput()->writeln(sprintf('Configs collected: %s with %s value(s).', $file, $valuesSet)); + if (0 === $valuesSet) { + continue; + } + + $this->getOutput()->writeln(sprintf( + 'Collected configuration values from %s with %s %s.', + $file, + $valuesSet, + $valuesSet === 1 ? 'value' : 'values' + )); } + $this->getOutput()->writeln(' '); // Add empty line to make output in terminal nicer. + return $buffer; } From d1221591c89e492ef1c943e0970cba5981166af8 Mon Sep 17 00:00:00 2001 From: Rouven Alexander Rieker Date: Thu, 18 Apr 2024 10:42:36 +0200 Subject: [PATCH 5/5] docs: update KEEP section --- docs/config-import.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/config-import.md b/docs/config-import.md index 202eea5..a54645f 100644 --- a/docs/config-import.md +++ b/docs/config-import.md @@ -99,11 +99,7 @@ vendorx/general/api_key: ### Keep Config -If you want to make sure that config value will not be affected by config importer - use `!!KEEP` magic-ish string. - -#### When would it be helpful? -When you have a bunch of same configs in different environments, but there one environment ( `X` env ) where you want to keep it as is, but not dispose the exact value of `X` env in config files. -Security sensitive data are good examples of such cases - you want them to be kept only in env DB, and definitely not in your GIT repo. +To ensure that a specific configuration value will not be changed by the config importer, please use the following string as configuration value: ```yaml vendorx/general/api_key: @@ -111,6 +107,7 @@ vendorx/general/api_key: 0: "!!KEEP" ``` +This is helpful when you've got the same settings across different environments but want to keep one environment ( `X` env) unchanged without showing the exact value in the config file. It's a common scenario, especially when dealing with sensitive data. You really should only keep that kind of info in the environment’s database, not in your GIT repo. ### Recursive folder setup