From ceff5cb9cabc7b5e7f9ae6adc8dbd48abe340b85 Mon Sep 17 00:00:00 2001 From: Aleksandr Vinokurov Date: Tue, 31 Mar 2015 20:21:04 +0000 Subject: [PATCH] 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 --- compose/cli/command.py | 25 +++++++++++-------------- compose/cli/errors.py | 2 +- compose/cli/utils.py | 19 +++++++++++++++++++ docs/cli.md | 9 +++++++-- tests/unit/cli_test.py | 41 +++++++++++++++++++++++------------------ 5 files changed, 61 insertions(+), 35 deletions(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index e829b25b2..bd6b2dc84 100644 --- a/compose/cli/command.py +++ b/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) diff --git a/compose/cli/errors.py b/compose/cli/errors.py index d439aa61c..9a909e469 100644 --- a/compose/cli/errors.py +++ b/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)) diff --git a/compose/cli/utils.py b/compose/cli/utils.py index d64eef4bc..5f5fed64e 100644 --- a/compose/cli/utils.py +++ b/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, diff --git a/docs/cli.md b/docs/cli.md index 30f821771..1b0fa852e 100644 --- a/docs/cli.md +++ b/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 diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index fcb55a673..bc49be4b8 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -62,30 +62,32 @@ 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', - ])) - - self.assertEqual('fig.yml', get_config_filename_for_files([ - 'fig.yml', - 'fig.yaml', - ])) - - self.assertEqual('fig.yaml', get_config_filename_for_files([ - 'fig.yaml', - ])) + """Test with files placed in the basedir""" + 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([])) + """Test with files placed in the subdir""" + + 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() command.base_dir = 'tests/fixtures/longer-filename-composefile' @@ -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)