From 99464d9c2bfc9052ca341dc0e57c0ab2760cd8a2 Mon Sep 17 00:00:00 2001 From: Klaas Hoekema Date: Sun, 16 Jun 2019 22:57:04 -0400 Subject: [PATCH 1/5] Handle environment file override within TopLevelCommand Several (but not all) of the subcommands are accepting and processing the `--env-file` option, but only because they need to look for a specific value in the environment. The work of applying the override makes more sense as the domain of TopLevelCommand, and moving it there and removing the option from the subcommands makes things simpler. Signed-off-by: Klaas Hoekema --- compose/cli/main.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 6391bd3ea..e11554ac0 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -247,6 +247,11 @@ class TopLevelCommand(object): def project_dir(self): return self.toplevel_options.get('--project-directory') or '.' + @property + def environment(self): + environment_file = self.toplevel_options.get('--env-file') + return Environment.from_env_file(self.project_dir, environment_file) + def build(self, options): """ Build or rebuild services. @@ -276,9 +281,7 @@ class TopLevelCommand(object): '--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_file = options.get('--env-file') - environment = Environment.from_env_file(self.project_dir, environment_file) - build_args = resolve_build_args(build_args, environment) + build_args = resolve_build_args(build_args, self.environment) self.project.build( service_names=options['SERVICE'], @@ -429,11 +432,8 @@ class TopLevelCommand(object): Compose file -t, --timeout TIMEOUT Specify a shutdown timeout in seconds. (default: 10) - --env-file PATH Specify an alternate environment file """ - environment_file = options.get('--env-file') - environment = Environment.from_env_file(self.project_dir, environment_file) - ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS') + ignore_orphans = self.environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and options['--remove-orphans']: raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.") @@ -489,11 +489,8 @@ class TopLevelCommand(object): -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_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') + use_cli = not self.environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') index = int(options.get('--index')) service = self.project.get_service(options['SERVICE']) detach = options.get('--detach') @@ -1051,7 +1048,6 @@ class TopLevelCommand(object): 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'] @@ -1066,9 +1062,7 @@ class TopLevelCommand(object): if detached and (cascade_stop or exit_value_from): raise UserError("--abort-on-container-exit and -d cannot be combined.") - environment_file = options.get('--env-file') - environment = Environment.from_env_file(self.project_dir, environment_file) - ignore_orphans = environment.get_boolean('COMPOSE_IGNORE_ORPHANS') + ignore_orphans = self.environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and remove_orphans: raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.") @@ -1360,7 +1354,7 @@ def run_one_off_container(container_options, project, service, options, toplevel if options['--rm']: project.client.remove_container(container.id, force=True, v=True) - environment_file = options.get('--env-file') + environment_file = toplevel_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() From 35eb40424c45209df3c23a33ae9831413abf7f26 Mon Sep 17 00:00:00 2001 From: Klaas Hoekema Date: Thu, 11 Jul 2019 22:28:18 -0400 Subject: [PATCH 2/5] Call TopLevelCommand's environment 'toplevel_environment' To help prevent confusion between the different meanings and sources of "environment", rename the method that loads the environment from the .env or --env-file (i.e. the one that applies at a project level) to 'toplevel_environment'. Signed-off-by: Klaas Hoekema --- compose/cli/main.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index e11554ac0..eb6e7d820 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -248,7 +248,7 @@ class TopLevelCommand(object): return self.toplevel_options.get('--project-directory') or '.' @property - def environment(self): + def toplevel_environment(self): environment_file = self.toplevel_options.get('--env-file') return Environment.from_env_file(self.project_dir, environment_file) @@ -281,7 +281,7 @@ class TopLevelCommand(object): '--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.' ) - build_args = resolve_build_args(build_args, self.environment) + build_args = resolve_build_args(build_args, self.toplevel_environment) self.project.build( service_names=options['SERVICE'], @@ -433,7 +433,7 @@ class TopLevelCommand(object): -t, --timeout TIMEOUT Specify a shutdown timeout in seconds. (default: 10) """ - ignore_orphans = self.environment.get_boolean('COMPOSE_IGNORE_ORPHANS') + ignore_orphans = self.toplevel_environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and options['--remove-orphans']: raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.") @@ -490,7 +490,7 @@ class TopLevelCommand(object): not supported in API < 1.25) -w, --workdir DIR Path to workdir directory for this command. """ - use_cli = not self.environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') + use_cli = not self.toplevel_environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') index = int(options.get('--index')) service = self.project.get_service(options['SERVICE']) detach = options.get('--detach') @@ -513,7 +513,7 @@ class TopLevelCommand(object): if IS_WINDOWS_PLATFORM or use_cli and not detach: sys.exit(call_docker( build_exec_command(options, container.id, command), - self.toplevel_options, environment) + self.toplevel_options, self.toplevel_environment) ) create_exec_options = { @@ -1062,7 +1062,7 @@ class TopLevelCommand(object): if detached and (cascade_stop or exit_value_from): raise UserError("--abort-on-container-exit and -d cannot be combined.") - ignore_orphans = self.environment.get_boolean('COMPOSE_IGNORE_ORPHANS') + ignore_orphans = self.toplevel_environment.get_boolean('COMPOSE_IGNORE_ORPHANS') if ignore_orphans and remove_orphans: raise UserError("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined.") @@ -1355,8 +1355,9 @@ def run_one_off_container(container_options, project, service, options, toplevel project.client.remove_container(container.id, force=True, v=True) environment_file = toplevel_options.get('--env-file') - environment = Environment.from_env_file(project_dir, environment_file) - use_cli = not environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') + toplevel_environment = Environment.from_env_file(project_dir, environment_file) + use_cli = not toplevel_environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') + signals.set_signal_handler_to_shutdown() signals.set_signal_handler_to_hang_up() try: @@ -1365,7 +1366,7 @@ def run_one_off_container(container_options, project, service, options, toplevel service.connect_container_to_networks(container, use_network_aliases) exit_code = call_docker( get_docker_start_call(container_options, container.id), - toplevel_options, environment + toplevel_options, toplevel_environment ) else: operation = RunOperation( From 088a798e7a546755537cb9082b440f17d64d6bdf Mon Sep 17 00:00:00 2001 From: Klaas Hoekema Date: Thu, 11 Jul 2019 23:27:11 -0400 Subject: [PATCH 3/5] Fix typo in 'split_env' error message Signed-off-by: Klaas Hoekema --- compose/config/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/config/environment.py b/compose/config/environment.py index e72c88231..696356f32 100644 --- a/compose/config/environment.py +++ b/compose/config/environment.py @@ -26,7 +26,7 @@ def split_env(env): key = env if re.search(r'\s', key): raise ConfigurationError( - "environment variable name '{}' may not contains whitespace.".format(key) + "environment variable name '{}' may not contain whitespace.".format(key) ) return key, value From 69c0683bfe8f4bbb90d07f0db6516e51942f97d6 Mon Sep 17 00:00:00 2001 From: Klaas Hoekema Date: Fri, 12 Jul 2019 12:59:31 -0400 Subject: [PATCH 4/5] Pass toplevel_environment to run_one_off_container Instead of passing `project_dir` from `TopLevelCommand.run` to `run_one_off_container` then using it there to load the toplevel environment (duplicating the logic that `TopLevelCommand.toplevel_environment` encapsulates), pass the Environment object. Signed-off-by: Klaas Hoekema --- compose/cli/main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index eb6e7d820..477b57b52 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -887,7 +887,7 @@ class TopLevelCommand(object): container_options = build_one_off_container_options(options, detach, command) run_one_off_container( container_options, self.project, service, options, - self.toplevel_options, self.project_dir + self.toplevel_options, self.toplevel_environment ) def scale(self, options): @@ -1325,7 +1325,7 @@ def build_one_off_container_options(options, detach, command): def run_one_off_container(container_options, project, service, options, toplevel_options, - project_dir='.'): + toplevel_environment): if not options['--no-deps']: deps = service.get_dependency_names() if deps: @@ -1354,8 +1354,6 @@ def run_one_off_container(container_options, project, service, options, toplevel if options['--rm']: project.client.remove_container(container.id, force=True, v=True) - environment_file = toplevel_options.get('--env-file') - toplevel_environment = Environment.from_env_file(project_dir, environment_file) use_cli = not toplevel_environment.get_boolean('COMPOSE_INTERACTIVE_NO_CLI') signals.set_signal_handler_to_shutdown() From 413e5db7b35684a543e1c14f89aad379167f9b06 Mon Sep 17 00:00:00 2001 From: Klaas Hoekema Date: Mon, 22 Jul 2019 13:59:49 -0400 Subject: [PATCH 5/5] Add shell completions for --env-file option Adds completions for the --env-file toplevel option to the bash, fish, and zsh completions files. Signed-off-by: Klaas Hoekema --- contrib/completion/bash/docker-compose | 5 +++++ contrib/completion/fish/docker-compose.fish | 1 + contrib/completion/zsh/_docker-compose | 2 ++ 3 files changed, 8 insertions(+) diff --git a/contrib/completion/bash/docker-compose b/contrib/completion/bash/docker-compose index e9168b1b1..6dc47799d 100644 --- a/contrib/completion/bash/docker-compose +++ b/contrib/completion/bash/docker-compose @@ -184,6 +184,10 @@ _docker_compose_docker_compose() { _filedir -d return ;; + --env-file) + _filedir + return + ;; $(__docker_compose_to_extglob "$daemon_options_with_args") ) return ;; @@ -612,6 +616,7 @@ _docker_compose() { --tlsverify " local daemon_options_with_args=" + --env-file --file -f --host -H --project-directory diff --git a/contrib/completion/fish/docker-compose.fish b/contrib/completion/fish/docker-compose.fish index 69ecc5056..0566e16ae 100644 --- a/contrib/completion/fish/docker-compose.fish +++ b/contrib/completion/fish/docker-compose.fish @@ -12,6 +12,7 @@ end complete -c docker-compose -s f -l file -r -d 'Specify an alternate compose file' complete -c docker-compose -s p -l project-name -x -d 'Specify an alternate project name' +complete -c docker-compose -l env-file -r -d 'Specify an alternate environment file (default: .env)' complete -c docker-compose -l verbose -d 'Show more output' complete -c docker-compose -s H -l host -x -d 'Daemon socket to connect to' complete -c docker-compose -l tls -d 'Use TLS; implied by --tlsverify' diff --git a/contrib/completion/zsh/_docker-compose b/contrib/completion/zsh/_docker-compose index 808b068a3..faf405988 100755 --- a/contrib/completion/zsh/_docker-compose +++ b/contrib/completion/zsh/_docker-compose @@ -341,6 +341,7 @@ _docker-compose() { '(- :)'{-h,--help}'[Get help]' \ '*'{-f,--file}"[${file_description}]:file:_files -g '*.yml'" \ '(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \ + '--env-file[Specify an alternate environment file (default: .env)]:env-file:_files' \ "--compatibility[If set, Compose will attempt to convert keys in v3 files to their non-Swarm equivalent]" \ '(- :)'{-v,--version}'[Print version and exit]' \ '--verbose[Show more output]' \ @@ -359,6 +360,7 @@ _docker-compose() { local -a relevant_compose_flags relevant_compose_repeatable_flags relevant_docker_flags compose_options docker_options relevant_compose_flags=( + "--env-file" "--file" "-f" "--host" "-H" "--project-name" "-p"