From f54167c68e3d71c480ef27650edcfc234db30b10 Mon Sep 17 00:00:00 2001 From: slowr Date: Fri, 22 Feb 2019 18:40:28 -0800 Subject: [PATCH] Added additional argument (--env-file) for docker-compose to import environment variables from a given PATH. Signed-off-by: Dimitrios Mavrommatis --- compose/cli/command.py | 6 ++++-- compose/cli/main.py | 19 ++++++++++++++----- compose/config/environment.py | 7 +++++-- tests/acceptance/cli_test.py | 15 +++++++++++++++ tests/fixtures/default-env-file/.env2 | 4 ++++ tests/unit/config/config_test.py | 19 +++++++++++++++++++ 6 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/default-env-file/.env2 diff --git a/compose/cli/command.py b/compose/cli/command.py index c1f6c29257b..f9e698e170b 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -39,7 +39,8 @@ def project_from_options(project_dir, options): override_dir = options.get('--project-directory') - environment = Environment.from_env_file(override_dir or project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(override_dir or project_dir, environment_file) environment.silent = options.get('COMMAND', None) in SILENT_COMMANDS set_parallel_limit(environment) @@ -77,7 +78,8 @@ def set_parallel_limit(environment): def get_config_from_options(base_dir, options): override_dir = options.get('--project-directory') - environment = Environment.from_env_file(override_dir or base_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(override_dir or base_dir, environment_file) config_path = get_config_path_from_options( base_dir, options, environment ) diff --git a/compose/cli/main.py b/compose/cli/main.py index 08976347ec2..d8793c85bc8 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -208,6 +208,7 @@ class TopLevelCommand(object): (default: the path of the Compose file) --compatibility If set, Compose will attempt to convert keys in v3 files to their non-Swarm equivalent + --env-file PATH Specify an alternate environment file Commands: build Build or rebuild services @@ -274,7 +275,8 @@ def build(self, options): '--build-arg is only supported when services are specified for API version < 1.25.' ' Please use a Compose file version > 2.2 or specify which services to build.' ) - environment = Environment.from_env_file(self.project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(self.project_dir, environment_file) build_args = resolve_build_args(build_args, environment) self.project.build( @@ -423,8 +425,10 @@ def down(self, options): Compose file -t, --timeout TIMEOUT Specify a shutdown timeout in seconds. (default: 10) + --env-file PATH Specify an alternate environment file """ - environment = Environment.from_env_file(self.project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(self.project_dir, environment_file) ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and options['--remove-orphans']: @@ -481,8 +485,10 @@ def exec_command(self, options): -e, --env KEY=VAL Set environment variables (can be used multiple times, not supported in API < 1.25) -w, --workdir DIR Path to workdir directory for this command. + --env-file PATH Specify an alternate environment file """ - environment = Environment.from_env_file(self.project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(self.project_dir, environment_file) use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') index = int(options.get('--index')) service = self.project.get_service(options['SERVICE']) @@ -1038,6 +1044,7 @@ def up(self, options): container. Implies --abort-on-container-exit. --scale SERVICE=NUM Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. + --env-file PATH Specify an alternate environment file """ start_deps = not options['--no-deps'] always_recreate_deps = options['--always-recreate-deps'] @@ -1052,7 +1059,8 @@ def up(self, options): if detached and (cascade_stop or exit_value_from): raise UserError("--abort-on-container-exit and -d cannot be combined.") - environment = Environment.from_env_file(self.project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(self.project_dir, environment_file) ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and remove_orphans: @@ -1345,7 +1353,8 @@ def remove_container(force=False): if options['--rm']: project.client.remove_container(container.id, force=True, v=True) - environment = Environment.from_env_file(project_dir) + environment_file = options.get('--env-file') + environment = Environment.from_env_file(project_dir, environment_file) use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') signals.set_signal_handler_to_shutdown() diff --git a/compose/config/environment.py b/compose/config/environment.py index 62c40a4bc06..e2db343d70a 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -59,12 +59,15 @@ def __init__(self, *args, **kwargs): self.silent = False @classmethod - def from_env_file(cls, base_dir): + def from_env_file(cls, base_dir, env_file=None): def _initialize(): result = cls() if base_dir is None: return result - env_file_path = os.path.join(base_dir, '.env') + if env_file: + env_file_path = os.path.join(base_dir, env_file) + else: + env_file_path = os.path.join(base_dir, '.env') try: return cls(env_vars_from_file(env_file_path)) except EnvFileNotFound: diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 8a6415b4088..6a9a392a555 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -324,6 +324,21 @@ def test_config_with_dot_env(self): 'version': '2.4' } + def test_config_with_env_file(self): + self.base_dir = 'tests/fixtures/default-env-file' + result = self.dispatch(['--env-file', '.env2', 'config']) + json_result = yaml.load(result.stdout) + assert json_result == { + 'services': { + 'web': { + 'command': 'false', + 'image': 'alpine:latest', + 'ports': ['5644/tcp', '9998/tcp'] + } + }, + 'version': '2.4' + } + def test_config_with_dot_env_and_override_dir(self): self.base_dir = 'tests/fixtures/default-env-file' result = self.dispatch(['--project-directory', 'alt/', 'config']) diff --git a/tests/fixtures/default-env-file/.env2 b/tests/fixtures/default-env-file/.env2 new file mode 100644 index 00000000000..d754523fc50 --- /dev/null +++ b/tests/fixtures/default-env-file/.env2 @@ -0,0 +1,4 @@ +IMAGE=alpine:latest +COMMAND=false +PORT1=5644 +PORT2=9998 diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index c2e6b7b0761..888c0ae5805 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -3465,6 +3465,25 @@ def test_config_file_with_environment_file(self): 'command': 'true' } + @mock.patch.dict(os.environ) + def test_config_file_with_options_environment_file(self): + project_dir = 'tests/fixtures/default-env-file' + service_dicts = config.load( + config.find( + project_dir, None, Environment.from_env_file(project_dir, '.env2') + ) + ).services + + assert service_dicts[0] == { + 'name': 'web', + 'image': 'alpine:latest', + 'ports': [ + types.ServicePort.parse('5644')[0], + types.ServicePort.parse('9998')[0] + ], + 'command': 'false' + } + @mock.patch.dict(os.environ) def test_config_file_with_environment_variable(self): project_dir = 'tests/fixtures/environment-interpolation'