diff --git a/compose/cli/main.py b/compose/cli/main.py index 624b007a8..972dffe20 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -122,7 +122,7 @@ def perform_command(options, handler, command_options): return project = project_from_options('.', options) - command = TopLevelCommand(project) + command = TopLevelCommand(project, options=options) with errors.handle_connection_errors(project.client): handler(command, command_options) @@ -157,16 +157,17 @@ def setup_console_handler(handler, verbose, noansi=False, level=None): if level is not None: levels = { - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL, + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL, } loglevel = levels.get(level.upper()) if loglevel is None: - raise UserError('Invalid value for --log-level. Expected one of ' - + 'DEBUG, INFO, WARNING, ERROR, CRITICAL.') + raise UserError( + 'Invalid value for --log-level. Expected one of DEBUG, INFO, WARNING, ERROR, CRITICAL.' + ) handler.setLevel(loglevel) @@ -237,9 +238,10 @@ class TopLevelCommand(object): version Show the Docker-Compose version information """ - def __init__(self, project, project_dir='.'): + def __init__(self, project, project_dir='.', options=None): self.project = project self.project_dir = '.' + self.toplevel_options = options or {} def build(self, options): """ @@ -475,7 +477,10 @@ class TopLevelCommand(object): tty = not options["-T"] if IS_WINDOWS_PLATFORM or use_cli and not detach: - sys.exit(call_docker(build_exec_command(options, container.id, command))) + sys.exit(call_docker( + build_exec_command(options, container.id, command), + self.toplevel_options) + ) create_exec_options = { "privileged": options["--privileged"], @@ -820,7 +825,10 @@ class TopLevelCommand(object): command = service.options.get('command') container_options = build_container_options(options, detach, command) - run_one_off_container(container_options, self.project, service, options, self.project_dir) + run_one_off_container( + container_options, self.project, service, options, + self.toplevel_options, self.project_dir + ) def scale(self, options): """ @@ -1253,7 +1261,8 @@ def build_container_options(options, detach, command): return container_options -def run_one_off_container(container_options, project, service, options, project_dir='.'): +def run_one_off_container(container_options, project, service, options, toplevel_options, + project_dir='.'): if not options['--no-deps']: deps = service.get_dependency_names() if deps: @@ -1289,7 +1298,10 @@ def run_one_off_container(container_options, project, service, options, project_ try: if IS_WINDOWS_PLATFORM or use_cli: service.connect_container_to_networks(container) - exit_code = call_docker(["start", "--attach", "--interactive", container.id]) + exit_code = call_docker( + ["start", "--attach", "--interactive", container.id], + toplevel_options + ) else: operation = RunOperation( project.client, @@ -1368,12 +1380,32 @@ def exit_if(condition, message, exit_code): raise SystemExit(exit_code) -def call_docker(args): +def call_docker(args, dockeropts): executable_path = find_executable('docker') if not executable_path: raise UserError(errors.docker_not_found_msg("Couldn't find `docker` binary.")) - args = [executable_path] + args + tls = dockeropts.get('--tls', False) + ca_cert = dockeropts.get('--tlscacert') + cert = dockeropts.get('--tlscert') + key = dockeropts.get('--tlskey') + verify = dockeropts.get('--tlsverify') + host = dockeropts.get('--host') + tls_options = [] + if tls: + tls_options.append('--tls') + if ca_cert: + tls_options.extend(['--tlscacert', ca_cert]) + if cert: + tls_options.extend(['--tlscert', cert]) + if key: + tls_options.extend(['--tlskey', key]) + if verify: + tls_options.append('--tlsverify') + if host: + tls_options.extend(['--host', host]) + + args = [executable_path] + tls_options + args log.debug(" ".join(map(pipes.quote, args))) return subprocess.call(args) diff --git a/tests/unit/cli/main_test.py b/tests/unit/cli/main_test.py index b1546d6f3..b46a3ee22 100644 --- a/tests/unit/cli/main_test.py +++ b/tests/unit/cli/main_test.py @@ -9,6 +9,7 @@ import pytest from compose import container from compose.cli.errors import UserError from compose.cli.formatter import ConsoleWarningFormatter +from compose.cli.main import call_docker from compose.cli.main import convergence_strategy_from_opts from compose.cli.main import filter_containers_to_service_names from compose.cli.main import setup_console_handler @@ -112,3 +113,44 @@ class TestConvergeStrategyFromOptsTestCase(object): convergence_strategy_from_opts(options) == ConvergenceStrategy.changed ) + + +def mock_find_executable(exe): + return exe + + +@mock.patch('compose.cli.main.find_executable', mock_find_executable) +class TestCallDocker(object): + def test_simple_no_options(self): + with mock.patch('subprocess.call') as fake_call: + call_docker(['ps'], {}) + + assert fake_call.call_args[0][0] == ['docker', 'ps'] + + def test_simple_tls_option(self): + with mock.patch('subprocess.call') as fake_call: + call_docker(['ps'], {'--tls': True}) + + assert fake_call.call_args[0][0] == ['docker', '--tls', 'ps'] + + def test_advanced_tls_options(self): + with mock.patch('subprocess.call') as fake_call: + call_docker(['ps'], { + '--tls': True, + '--tlscacert': './ca.pem', + '--tlscert': './cert.pem', + '--tlskey': './key.pem', + }) + + assert fake_call.call_args[0][0] == [ + 'docker', '--tls', '--tlscacert', './ca.pem', '--tlscert', + './cert.pem', '--tlskey', './key.pem', 'ps' + ] + + def test_with_host_option(self): + with mock.patch('subprocess.call') as fake_call: + call_docker(['ps'], {'--host': 'tcp://mydocker.net:2333'}) + + assert fake_call.call_args[0][0] == [ + 'docker', '--host', 'tcp://mydocker.net:2333', 'ps' + ]