Merge pull request #5722 from docker/5700-call-docker-with-args

Re-use TLS and host options when shelling out to docker CLI
This commit is contained in:
Joffrey F 2018-02-27 12:27:49 -08:00 committed by GitHub
commit 9b2efd50f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 57 deletions

View File

@ -116,13 +116,13 @@ def perform_command(options, handler, command_options):
handler(command_options) handler(command_options)
return return
if options['COMMAND'] in ('config', 'bundle'): if options['COMMAND'] == 'config':
command = TopLevelCommand(None) command = TopLevelCommand(None, options=options)
handler(command, options, command_options) handler(command, command_options)
return return
project = project_from_options('.', options) project = project_from_options('.', options)
command = TopLevelCommand(project) command = TopLevelCommand(project, options=options)
with errors.handle_connection_errors(project.client): with errors.handle_connection_errors(project.client):
handler(command, command_options) handler(command, command_options)
@ -165,8 +165,9 @@ def setup_console_handler(handler, verbose, noansi=False, level=None):
} }
loglevel = levels.get(level.upper()) loglevel = levels.get(level.upper())
if loglevel is None: if loglevel is None:
raise UserError('Invalid value for --log-level. Expected one of ' raise UserError(
+ 'DEBUG, INFO, WARNING, ERROR, CRITICAL.') 'Invalid value for --log-level. Expected one of DEBUG, INFO, WARNING, ERROR, CRITICAL.'
)
handler.setLevel(loglevel) handler.setLevel(loglevel)
@ -237,9 +238,10 @@ class TopLevelCommand(object):
version Show the Docker-Compose version information 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 = project
self.project_dir = '.' self.project_dir = '.'
self.toplevel_options = options or {}
def build(self, options): def build(self, options):
""" """
@ -277,7 +279,7 @@ class TopLevelCommand(object):
memory=options.get('--memory'), memory=options.get('--memory'),
build_args=build_args) build_args=build_args)
def bundle(self, config_options, options): def bundle(self, options):
""" """
Generate a Distributed Application Bundle (DAB) from the Compose file. Generate a Distributed Application Bundle (DAB) from the Compose file.
@ -296,8 +298,7 @@ class TopLevelCommand(object):
-o, --output PATH Path to write the bundle file to. -o, --output PATH Path to write the bundle file to.
Defaults to "<project name>.dab". Defaults to "<project name>.dab".
""" """
self.project = project_from_options('.', config_options) compose_config = get_config_from_options(self.project_dir, self.toplevel_options)
compose_config = get_config_from_options(self.project_dir, config_options)
output = options["--output"] output = options["--output"]
if not output: if not output:
@ -310,7 +311,7 @@ class TopLevelCommand(object):
log.info("Wrote bundle to {}".format(output)) log.info("Wrote bundle to {}".format(output))
def config(self, config_options, options): def config(self, options):
""" """
Validate and view the Compose file. Validate and view the Compose file.
@ -325,11 +326,12 @@ class TopLevelCommand(object):
""" """
compose_config = get_config_from_options(self.project_dir, config_options) compose_config = get_config_from_options(self.project_dir, self.toplevel_options)
image_digests = None image_digests = None
if options['--resolve-image-digests']: if options['--resolve-image-digests']:
self.project = project_from_options('.', config_options) self.project = project_from_options('.', self.toplevel_options)
with errors.handle_connection_errors(self.project.client):
image_digests = image_digests_for_project(self.project) image_digests = image_digests_for_project(self.project)
if options['--quiet']: if options['--quiet']:
@ -475,7 +477,10 @@ class TopLevelCommand(object):
tty = not options["-T"] tty = not options["-T"]
if IS_WINDOWS_PLATFORM or use_cli and not detach: 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 = { create_exec_options = {
"privileged": options["--privileged"], "privileged": options["--privileged"],
@ -820,7 +825,10 @@ class TopLevelCommand(object):
command = service.options.get('command') command = service.options.get('command')
container_options = build_container_options(options, detach, 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): def scale(self, options):
""" """
@ -1136,7 +1144,6 @@ def timeout_from_opts(options):
def image_digests_for_project(project, allow_push=False): def image_digests_for_project(project, allow_push=False):
with errors.handle_connection_errors(project.client):
try: try:
return get_image_digests( return get_image_digests(
project, project,
@ -1253,7 +1260,8 @@ def build_container_options(options, detach, command):
return container_options 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']: if not options['--no-deps']:
deps = service.get_dependency_names() deps = service.get_dependency_names()
if deps: if deps:
@ -1289,7 +1297,10 @@ def run_one_off_container(container_options, project, service, options, project_
try: try:
if IS_WINDOWS_PLATFORM or use_cli: if IS_WINDOWS_PLATFORM or use_cli:
service.connect_container_to_networks(container) 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: else:
operation = RunOperation( operation = RunOperation(
project.client, project.client,
@ -1368,12 +1379,32 @@ def exit_if(condition, message, exit_code):
raise SystemExit(exit_code) raise SystemExit(exit_code)
def call_docker(args): def call_docker(args, dockeropts):
executable_path = find_executable('docker') executable_path = find_executable('docker')
if not executable_path: if not executable_path:
raise UserError(errors.docker_not_found_msg("Couldn't find `docker` binary.")) 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))) log.debug(" ".join(map(pipes.quote, args)))
return subprocess.call(args) return subprocess.call(args)

View File

@ -9,6 +9,7 @@ import pytest
from compose import container from compose import container
from compose.cli.errors import UserError from compose.cli.errors import UserError
from compose.cli.formatter import ConsoleWarningFormatter 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 convergence_strategy_from_opts
from compose.cli.main import filter_containers_to_service_names from compose.cli.main import filter_containers_to_service_names
from compose.cli.main import setup_console_handler from compose.cli.main import setup_console_handler
@ -112,3 +113,44 @@ class TestConvergeStrategyFromOptsTestCase(object):
convergence_strategy_from_opts(options) == convergence_strategy_from_opts(options) ==
ConvergenceStrategy.changed 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'
]