Repository: symfony/flex (php)

Issue:
Suggest adding management of the compose.yml file in addition to docker-compose.yml
Hello !

Following my use of Symfony Flex and its wonderful ability to manage Docker configurations via recipes, I noticed that the manifest supports the docker-compose.yml file.

However, consulting the official Docker documentation, it seems that the use of the docker-compose.yml file may be gradually deprecated or, at least, that there is an alternative under the name compose.yml. Details can be found on this page: https://docs.docker.com/compose/multiple-compose-files/merge/

Proposal:

- Add support for compose.yml to the Symfony Flex manifest.
- Check whether a docker-compose.yml already exists and use it, otherwise create a compose.yml.

This will ensure better compatibility with future versions of Docker Compose and offer greater flexibility to developers using Symfony Flex.

I'd love to hear your thoughts on this and whether this is something you'd consider adding to Symfony Flex.

Thank you for your time and consideration!

Pull request description:
feat: support for compose.yml
Docker Compose now recommends using a definition file named `compose.yaml` and also supports `compose.yml` (the legacy `docker-compose.ya?ml` are still supported): https://docs.docker.com/compose/compose-file/03-compose-file/

This PR adds support for `compose.ya?ml` file to Flex, and changes the name of the generated file to `compose.yaml`, according to Docker best practices.

Closes #992.

Patch:
diff --git a/src/Configurator/DockerComposeConfigurator.php b/src/Configurator/DockerComposeConfigurator.php
index debe924..1f7589f 100644
--- a/src/Configurator/DockerComposeConfigurator.php
+++ b/src/Configurator/DockerComposeConfigurator.php
@@ -23,9 +23,9 @@ use Symfony\Flex\Recipe;
 use Symfony\Flex\Update\RecipeUpdate;
 
 /**
- * Adds services and volumes to docker-compose.yml file.
+ * Adds services and volumes to compose.yaml file.
  *
- * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Kévin Dunglas <kevin@dunglas.dev>
  */
 class DockerComposeConfigurator extends AbstractConfigurator
 {
@@ -145,9 +145,20 @@ class DockerComposeConfigurator extends AbstractConfigurator
      */
     private function normalizeConfig(array $config): array
     {
-        foreach ($config as $val) {
-            // Support for the short syntax recipe syntax that modifies docker-compose.yml only
-            return isset($val[0]) ? ['docker-compose.yml' => $config] : $config;
+        foreach ($config as $key => $val) {
+            // Support for the short recipe syntax that modifies compose.yaml only
+            if (isset($val[0])) {
+                return ['compose.yaml' => $config];
+            }
+
+            if (!str_starts_with($key, 'docker-')) {
+                continue;
+            }
+
+            // If the recipe still use the legacy "docker-compose.yml" names, remove the "docker-" prefix and change the extension
+            $newKey = pathinfo(substr($key, 7), \PATHINFO_FILENAME).'.yaml';
+            $config[$newKey] = $val;
+            unset($config[$key]);
         }
 
         return $config;
@@ -159,11 +170,13 @@ class DockerComposeConfigurator extends AbstractConfigurator
     private function findDockerComposeFile(string $rootDir, string $file): ?string
     {
         if (isset($_SERVER['COMPOSE_FILE'])) {
+            $filenameToFind = pathinfo($file, \PATHINFO_FILENAME);
             $separator = $_SERVER['COMPOSE_PATH_SEPARATOR'] ?? ('\\' === \DIRECTORY_SEPARATOR ? ';' : ':');
 
             $files = explode($separator, $_SERVER['COMPOSE_FILE']);
             foreach ($files as $f) {
-                if ($file !== basename($f)) {
+                $filename = pathinfo($f, \PATHINFO_FILENAME);
+                if ($filename !== $filenameToFind && "docker-$filenameToFind" !== $filename) {
                     continue;
                 }
 
@@ -180,10 +193,13 @@ class DockerComposeConfigurator extends AbstractConfigurator
         // COMPOSE_FILE not set, or doesn't contain the file we're looking for
         $dir = $rootDir;
         do {
-            // Test with the ".yaml" extension if the file doesn't end up with ".yml".
             if (
                 $this->filesystem->exists($dockerComposeFile = sprintf('%s/%s', $dir, $file)) ||
-                $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -2).'aml')
+                // Test with the ".yml" extension if the file doesn't end up with ".yaml"
+                $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml') ||
+                // Test with the legacy "docker-" suffix if "compose.ya?ml" doesn't exist
+                $this->filesystem->exists($dockerComposeFile = sprintf('%s/docker-%s', $dir, $file)) ||
+                $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml')
             ) {
                 return $dockerComposeFile;
             }
@@ -359,7 +375,7 @@ class DockerComposeConfigurator extends AbstractConfigurator
         $io->writeError(sprintf('  - <warning> %s </> %s', $warning, $recipe->getFormattedOrigin()));
         $question = '    The recipe for this package contains some Docker configuration.
 
-    This may create/update <comment>docker-compose.yml</comment> or update <comment>Dockerfile</comment> (if it exists).
+    This may create/update <comment>compose.yaml</comment> or update <comment>Dockerfile</comment> (if it exists).
 
     Do you want to include Docker configuration from recipes?
     [<comment>y</>] Yes


Test patch:
diff --git a/tests/Configurator/DockerComposeConfiguratorTest.php b/tests/Configurator/DockerComposeConfiguratorTest.php
index 4539168..114249d 100644
--- a/tests/Configurator/DockerComposeConfiguratorTest.php
+++ b/tests/Configurator/DockerComposeConfiguratorTest.php
@@ -23,7 +23,7 @@ use Symfony\Flex\Recipe;
 use Symfony\Flex\Update\RecipeUpdate;
 
 /**
- * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Kévin Dunglas <kevin@dunglas.dev>
  */
 class DockerComposeConfiguratorTest extends TestCase
 {
@@ -166,18 +166,33 @@ YAML;
         putenv('COMPOSER='.$this->originalEnvComposer);
 
         (new Filesystem())->remove([
+            FLEX_TEST_DIR.'/compose.yml',
             FLEX_TEST_DIR.'/docker-compose.yml',
+            FLEX_TEST_DIR.'/compose.override.yml',
             FLEX_TEST_DIR.'/docker-compose.override.yml',
+            FLEX_TEST_DIR.'/compose.yaml',
             FLEX_TEST_DIR.'/docker-compose.yaml',
             FLEX_TEST_DIR.'/composer.json',
+            FLEX_TEST_DIR.'/child/compose.override.yaml',
             FLEX_TEST_DIR.'/child/docker-compose.override.yaml',
             FLEX_TEST_DIR.'/child',
         ]);
     }
 
-    public function testConfigure()
+    public static function dockerComposerFileProvider(): iterable
     {
-        $dockerComposeFile = FLEX_TEST_DIR.'/docker-compose.yaml';
+        yield ['compose.yaml'];
+        yield ['compose.yml'];
+        yield ['docker-compose.yaml'];
+        yield ['docker-compose.yml'];
+    }
+
+    /**
+     * @dataProvider dockerComposerFileProvider
+     */
+    public function testConfigure(string $fileName)
+    {
+        $dockerComposeFile = FLEX_TEST_DIR."/$fileName";
         file_put_contents($dockerComposeFile, self::ORIGINAL_CONTENT);
 
         $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
@@ -205,7 +220,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-            );
+        );
 
         $this->configurator->unconfigure($this->recipeDb, self::CONFIG_DB, $this->lock);
         $this->assertEquals(self::ORIGINAL_CONTENT, file_get_contents($dockerComposeFile));
@@ -216,7 +231,7 @@ YAML
         $this->package->setExtra(['symfony' => ['docker' => false]]);
         $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
 
-        $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/docker-compose.yml');
+        $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/docker-compose.yaml');
     }
 
     /**
@@ -235,9 +250,9 @@ YAML
         $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
 
         if ($expectedIsConfigured) {
-            $this->assertFileExists(FLEX_TEST_DIR.'/docker-compose.yml');
+            $this->assertFileExists(FLEX_TEST_DIR.'/compose.yaml');
         } else {
-            $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/docker-compose.yml');
+            $this->assertFileDoesNotExist(FLEX_TEST_DIR.'/compose.yaml');
         }
 
         $composerJsonData = json_decode(file_get_contents($composerJsonPath), true);
@@ -270,7 +285,7 @@ YAML
         $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
         unset($_SERVER['SYMFONY_DOCKER']);
 
-        $this->assertFileExists(FLEX_TEST_DIR.'/docker-compose.yml');
+        $this->assertFileExists(FLEX_TEST_DIR.'/compose.yaml');
 
         $composerJsonData = json_decode(file_get_contents($composerJsonPath), true);
         $this->assertArrayHasKey('extra', $composerJsonData);
@@ -316,7 +331,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-            );
+        );
 
         $this->configurator->unconfigure($this->recipeDb, self::CONFIG_DB, $this->lock);
         // Not the same original, we have an extra breaks line
@@ -411,7 +426,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-            );
+        );
 
         $this->configurator->unconfigure($recipe, $config, $this->lock);
         $this->assertEquals($originalContent, file_get_contents($dockerComposeFile));
@@ -526,7 +541,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-                );
+            );
         }
 
         $this->configurator->unconfigure($this->recipeDb, self::CONFIG_DB_MULTIPLE_FILES, $this->lock);
@@ -571,7 +586,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-                );
+            );
         }
 
         $this->configurator->unconfigure($this->recipeDb, self::CONFIG_DB_MULTIPLE_FILES, $this->lock);
@@ -624,7 +639,7 @@ YAML
 
     public function testConfigureWithoutExistingDockerComposeFiles()
     {
-        $dockerComposeFile = FLEX_TEST_DIR.'/docker-compose.yml';
+        $dockerComposeFile = FLEX_TEST_DIR.'/compose.yaml';
         $defaultContent = "version: '3'\n";
 
         $this->configurator->configure($this->recipeDb, self::CONFIG_DB, $this->lock);
@@ -653,7 +668,7 @@ volumes:
 ###< doctrine/doctrine-bundle ###
 
 YAML
-            );
+        );
 
         $this->configurator->unconfigure($this->recipeDb, self::CONFIG_DB, $this->lock);
         $this->assertEquals(trim($defaultContent), file_get_contents($dockerComposeFile));