Przeglądaj źródła

Add parent directories search for default compose-files

Does not change directory to the parent with the compose-file found.
Works like passing '--file' or setting 'COMPOSE_FILE' with absolute path.
Resolves issue #946.

Signed-off-by: Aleksandr Vinokurov <[email protected]>
Aleksandr Vinokurov 10 lat temu
rodzic
commit
ceff5cb9ca
5 zmienionych plików z 60 dodań i 34 usunięć
  1. 11 14
      compose/cli/command.py
  2. 1 1
      compose/cli/errors.py
  3. 19 0
      compose/cli/utils.py
  4. 7 2
      docs/cli.md
  5. 22 17
      tests/unit/cli_test.py

+ 11 - 14
compose/cli/command.py

@@ -10,7 +10,7 @@ from .. import config
 from ..project import Project
 from ..service import ConfigError
 from .docopt_command import DocoptCommand
-from .utils import call_silently, is_mac, is_ubuntu
+from .utils import call_silently, is_mac, is_ubuntu, find_candidates_in_parent_dirs
 from .docker_client import docker_client
 from . import verbose_proxy
 from . import errors
@@ -18,6 +18,13 @@ from .. import __version__
 
 log = logging.getLogger(__name__)
 
+SUPPORTED_FILENAMES = [
+    'docker-compose.yml',
+    'docker-compose.yaml',
+    'fig.yml',
+    'fig.yaml',
+]
+
 
 class Command(DocoptCommand):
     base_dir = '.'
@@ -100,20 +107,10 @@ class Command(DocoptCommand):
         if file_path:
             return os.path.join(self.base_dir, file_path)
 
-        supported_filenames = [
-            'docker-compose.yml',
-            'docker-compose.yaml',
-            'fig.yml',
-            'fig.yaml',
-        ]
-
-        def expand(filename):
-            return os.path.join(self.base_dir, filename)
-
-        candidates = [filename for filename in supported_filenames if os.path.exists(expand(filename))]
+        (candidates, path) = find_candidates_in_parent_dirs(SUPPORTED_FILENAMES, self.base_dir)
 
         if len(candidates) == 0:
-            raise errors.ComposeFileNotFound(supported_filenames)
+            raise errors.ComposeFileNotFound(SUPPORTED_FILENAMES)
 
         winner = candidates[0]
 
@@ -130,4 +127,4 @@ class Command(DocoptCommand):
             log.warning("%s is deprecated and will not be supported in future. "
                         "Please rename your config file to docker-compose.yml\n" % winner)
 
-        return expand(winner)
+        return os.path.join(path, winner)

+ 1 - 1
compose/cli/errors.py

@@ -58,7 +58,7 @@ class ConnectionErrorGeneric(UserError):
 class ComposeFileNotFound(UserError):
     def __init__(self, supported_filenames):
         super(ComposeFileNotFound, self).__init__("""
-        Can't find a suitable configuration file. Are you in the right directory?
+        Can't find a suitable configuration file in this directory or any parent. Are you in the right directory?
 
         Supported filenames: %s
         """ % ", ".join(supported_filenames))

+ 19 - 0
compose/cli/utils.py

@@ -62,6 +62,25 @@ def mkdir(path, permissions=0o700):
     return path
 
 
+def find_candidates_in_parent_dirs(filenames, path):
+    """
+    Given a directory path to start, looks for filenames in the
+    directory, and then each parent directory successively,
+    until found.
+
+    Returns tuple (candidates, path).
+    """
+    candidates = [filename for filename in filenames
+                  if os.path.exists(os.path.join(path, filename))]
+
+    if len(candidates) == 0:
+        parent_dir = os.path.join(path, '..')
+        if os.path.abspath(parent_dir) != os.path.abspath(path):
+            return find_candidates_in_parent_dirs(filenames, parent_dir)
+
+    return (candidates, path)
+
+
 def split_buffer(reader, separator):
     """
     Given a generator which yields strings and a separator string,

+ 7 - 2
docs/cli.md

@@ -136,7 +136,10 @@ By default, if there are existing containers for a service, `docker-compose up`
 
 ### -f, --file FILE
 
- Specifies an alternate Compose yaml file (default: `docker-compose.yml`)
+ Specify what file to read configuration from. If not provided, Compose will look
+ for `docker-compose.yml` in the current working directory, and then each parent
+ directory successively, until found.
+
 
 ### -p, --project-name NAME
 
@@ -157,7 +160,9 @@ Sets the project name, which is prepended to the name of every container started
 
 ### COMPOSE\_FILE
 
-Sets the path to the `docker-compose.yml` to use. Defaults to `docker-compose.yml` in the current working directory.
+Specify what file to read configuration from. If not provided, Compose will look
+for `docker-compose.yml` in the current working directory, and then each parent
+directory successively, until found.
 
 ### DOCKER\_HOST
 

+ 22 - 17
tests/unit/cli_test.py

@@ -62,29 +62,31 @@ class CLITestCase(unittest.TestCase):
         self.assertEquals(project_name, name)
 
     def test_filename_check(self):
-        self.assertEqual('docker-compose.yml', get_config_filename_for_files([
+        files = [
             'docker-compose.yml',
             'docker-compose.yaml',
             'fig.yml',
             'fig.yaml',
-        ]))
+        ]
 
-        self.assertEqual('docker-compose.yaml', get_config_filename_for_files([
-            'docker-compose.yaml',
-            'fig.yml',
-            'fig.yaml',
-        ]))
+        """Test with files placed in the basedir"""
 
-        self.assertEqual('fig.yml', get_config_filename_for_files([
-            'fig.yml',
-            'fig.yaml',
-        ]))
+        self.assertEqual('docker-compose.yml', get_config_filename_for_files(files[0:]))
+        self.assertEqual('docker-compose.yaml', get_config_filename_for_files(files[1:]))
+        self.assertEqual('fig.yml', get_config_filename_for_files(files[2:]))
+        self.assertEqual('fig.yaml', get_config_filename_for_files(files[3:]))
+        self.assertRaises(ComposeFileNotFound, lambda: get_config_filename_for_files([]))
 
-        self.assertEqual('fig.yaml', get_config_filename_for_files([
-            'fig.yaml',
-        ]))
+        """Test with files placed in the subdir"""
 
-        self.assertRaises(ComposeFileNotFound, lambda: get_config_filename_for_files([]))
+        def get_config_filename_for_files_in_subdir(files):
+            return get_config_filename_for_files(files, subdir=True)
+
+        self.assertEqual('docker-compose.yml', get_config_filename_for_files_in_subdir(files[0:]))
+        self.assertEqual('docker-compose.yaml', get_config_filename_for_files_in_subdir(files[1:]))
+        self.assertEqual('fig.yml', get_config_filename_for_files_in_subdir(files[2:]))
+        self.assertEqual('fig.yaml', get_config_filename_for_files_in_subdir(files[3:]))
+        self.assertRaises(ComposeFileNotFound, lambda: get_config_filename_for_files_in_subdir([]))
 
     def test_get_project(self):
         command = TopLevelCommand()
@@ -135,12 +137,15 @@ class CLITestCase(unittest.TestCase):
             {'FOO': 'ONE', 'BAR': 'NEW', 'OTHER': 'THREE'})
 
 
-def get_config_filename_for_files(filenames):
+def get_config_filename_for_files(filenames, subdir=None):
     project_dir = tempfile.mkdtemp()
     try:
         make_files(project_dir, filenames)
         command = TopLevelCommand()
-        command.base_dir = project_dir
+        if subdir:
+            command.base_dir = tempfile.mkdtemp(dir=project_dir)
+        else:
+            command.base_dir = project_dir
         return os.path.basename(command.get_config_path())
     finally:
         shutil.rmtree(project_dir)