diff --git a/src/Configurator/DockerComposeConfigurator.php b/src/Configurator/DockerComposeConfigurator.php index e1f07c33..84b8afd2 100644 --- a/src/Configurator/DockerComposeConfigurator.php +++ b/src/Configurator/DockerComposeConfigurator.php @@ -277,6 +277,13 @@ private function configureDockerCompose(Recipe $recipe, array $config, bool $upd // Keep end in memory (check break line on previous line) if (null !== $node) { $endAt[$node] = !$i || '' !== trim($lines[$i - 1]) ? $i : $i - 1; + // Prune nodesLines[old node] to match the range the splice will replace. + if (isset($nodesLines[$node][$i])) { + unset($nodesLines[$node][$i]); + if ('' === trim($lines[$i - 1])) { + unset($nodesLines[$node][$i - 1]); + } + } } $node = $matches[1]; if (!isset($nodesLines[$node])) { @@ -307,14 +314,15 @@ private function configureDockerCompose(Recipe $recipe, array $config, bool $upd array_splice($lines, $startAt[$key], $length, ltrim($updatedContents, "\n")); // reset any start/end positions after this to the new positions + $shift = $length - 1; foreach ($startAt as $sectionKey => $at) { if ($at > $originalEndAt) { - $startAt[$sectionKey] = $at - $length - 1; + $startAt[$sectionKey] = $at - $shift; } } foreach ($endAt as $sectionKey => $at) { if ($at > $originalEndAt) { - $endAt[$sectionKey] = $at - $length; + $endAt[$sectionKey] = $at - $shift; } } diff --git a/tests/Configurator/DockerComposeConfiguratorTest.php b/tests/Configurator/DockerComposeConfiguratorTest.php index 7c2dec88..efaa9f32 100644 --- a/tests/Configurator/DockerComposeConfiguratorTest.php +++ b/tests/Configurator/DockerComposeConfiguratorTest.php @@ -209,6 +209,29 @@ public function testConfigure(string $fileName) $this->assertEquals(self::ORIGINAL_CONTENT, file_get_contents($dockerComposeFile)); } + public function testReconfigureDoesNotDuplicateLaterTopLevelKey() + { + $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock); + + $servicesOnlyRecipe = $this->getMockBuilder(Recipe::class)->disableOriginalConstructor()->getMock(); + $servicesOnlyRecipe->method('getName')->willReturn('acme/services-only'); + $servicesOnlyConfig = [ + 'services' => [ + 'cache:', + ' image: redis:alpine', + ], + ]; + + $this->configurator->configure($servicesOnlyRecipe, $servicesOnlyConfig, $this->lock); + $afterFirstConfigure = file_get_contents(FLEX_TEST_DIR.'/compose.yaml'); + + $this->configurator->configure($servicesOnlyRecipe, $servicesOnlyConfig, $this->lock, ['force' => true]); + $afterSecondConfigure = file_get_contents(FLEX_TEST_DIR.'/compose.yaml'); + + $this->assertSame(1, substr_count($afterSecondConfigure, "\nvolumes:\n"), 'compose.yaml must contain exactly one top-level "volumes:" key'); + $this->assertSame($afterFirstConfigure, $afterSecondConfigure, 'configure must be idempotent when re-run'); + } + public function testNotConfiguredIfConfigSet() { $this->package->setExtra(['symfony' => ['docker' => false]]);