mirror of
https://github.com/docker/compose.git
synced 2025-04-08 17:05:13 +02:00
Merge pull request #7202 from aiordache/devtool28_compose_docker_contexts
Implement docker contexts to target different docker engines
This commit is contained in:
commit
13bacba2b9
@ -8,7 +8,6 @@ import re
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from . import errors
|
from . import errors
|
||||||
from . import verbose_proxy
|
|
||||||
from .. import config
|
from .. import config
|
||||||
from .. import parallel
|
from .. import parallel
|
||||||
from ..config.environment import Environment
|
from ..config.environment import Environment
|
||||||
@ -17,10 +16,10 @@ from ..const import LABEL_CONFIG_FILES
|
|||||||
from ..const import LABEL_ENVIRONMENT_FILE
|
from ..const import LABEL_ENVIRONMENT_FILE
|
||||||
from ..const import LABEL_WORKING_DIR
|
from ..const import LABEL_WORKING_DIR
|
||||||
from ..project import Project
|
from ..project import Project
|
||||||
from .docker_client import docker_client
|
from .docker_client import get_client
|
||||||
from .docker_client import get_tls_version
|
from .docker_client import load_context
|
||||||
from .docker_client import tls_config_from_options
|
from .docker_client import make_context
|
||||||
from .utils import get_version_info
|
from .errors import UserError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -48,16 +47,28 @@ def project_from_options(project_dir, options, additional_options=None):
|
|||||||
environment.silent = options.get('COMMAND', None) in SILENT_COMMANDS
|
environment.silent = options.get('COMMAND', None) in SILENT_COMMANDS
|
||||||
set_parallel_limit(environment)
|
set_parallel_limit(environment)
|
||||||
|
|
||||||
host = options.get('--host')
|
# get the context for the run
|
||||||
|
context = None
|
||||||
|
context_name = options.get('--context', None)
|
||||||
|
if context_name:
|
||||||
|
context = load_context(context_name)
|
||||||
|
if not context:
|
||||||
|
raise UserError("Context '{}' not found".format(context_name))
|
||||||
|
|
||||||
|
host = options.get('--host', None)
|
||||||
if host is not None:
|
if host is not None:
|
||||||
|
if context:
|
||||||
|
raise UserError(
|
||||||
|
"-H, --host and -c, --context are mutually exclusive. Only one should be set.")
|
||||||
host = host.lstrip('=')
|
host = host.lstrip('=')
|
||||||
|
context = make_context(host, options, environment)
|
||||||
|
|
||||||
return get_project(
|
return get_project(
|
||||||
project_dir,
|
project_dir,
|
||||||
get_config_path_from_options(project_dir, options, environment),
|
get_config_path_from_options(project_dir, options, environment),
|
||||||
project_name=options.get('--project-name'),
|
project_name=options.get('--project-name'),
|
||||||
verbose=options.get('--verbose'),
|
verbose=options.get('--verbose'),
|
||||||
host=host,
|
context=context,
|
||||||
tls_config=tls_config_from_options(options, environment),
|
|
||||||
environment=environment,
|
environment=environment,
|
||||||
override_dir=override_dir,
|
override_dir=override_dir,
|
||||||
compatibility=compatibility_from_options(project_dir, options, environment),
|
compatibility=compatibility_from_options(project_dir, options, environment),
|
||||||
@ -112,25 +123,8 @@ def get_config_path_from_options(base_dir, options, environment):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_client(environment, verbose=False, version=None, tls_config=None, host=None,
|
|
||||||
tls_version=None):
|
|
||||||
|
|
||||||
client = docker_client(
|
|
||||||
version=version, tls_config=tls_config, host=host,
|
|
||||||
environment=environment, tls_version=get_tls_version(environment)
|
|
||||||
)
|
|
||||||
if verbose:
|
|
||||||
version_info = six.iteritems(client.version())
|
|
||||||
log.info(get_version_info('full'))
|
|
||||||
log.info("Docker base_url: %s", client.base_url)
|
|
||||||
log.info("Docker version: %s",
|
|
||||||
", ".join("%s=%s" % item for item in version_info))
|
|
||||||
return verbose_proxy.VerboseProxy('docker', client)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
|
||||||
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
||||||
host=None, tls_config=None, environment=None, override_dir=None,
|
context=None, environment=None, override_dir=None,
|
||||||
compatibility=False, interpolate=True, environment_file=None):
|
compatibility=False, interpolate=True, environment_file=None):
|
||||||
if not environment:
|
if not environment:
|
||||||
environment = Environment.from_env_file(project_dir)
|
environment = Environment.from_env_file(project_dir)
|
||||||
@ -145,8 +139,7 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False,
|
|||||||
API_VERSIONS[config_data.version])
|
API_VERSIONS[config_data.version])
|
||||||
|
|
||||||
client = get_client(
|
client = get_client(
|
||||||
verbose=verbose, version=api_version, tls_config=tls_config,
|
verbose=verbose, version=api_version, context=context, environment=environment
|
||||||
host=host, environment=environment
|
|
||||||
)
|
)
|
||||||
|
|
||||||
with errors.handle_connection_errors(client):
|
with errors.handle_connection_errors(client):
|
||||||
|
@ -5,17 +5,22 @@ import logging
|
|||||||
import os.path
|
import os.path
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
|
import six
|
||||||
from docker import APIClient
|
from docker import APIClient
|
||||||
|
from docker import Context
|
||||||
|
from docker import ContextAPI
|
||||||
|
from docker import TLSConfig
|
||||||
from docker.errors import TLSParameterError
|
from docker.errors import TLSParameterError
|
||||||
from docker.tls import TLSConfig
|
|
||||||
from docker.utils import kwargs_from_env
|
from docker.utils import kwargs_from_env
|
||||||
from docker.utils.config import home_dir
|
from docker.utils.config import home_dir
|
||||||
|
|
||||||
|
from . import verbose_proxy
|
||||||
from ..config.environment import Environment
|
from ..config.environment import Environment
|
||||||
from ..const import HTTP_TIMEOUT
|
from ..const import HTTP_TIMEOUT
|
||||||
from ..utils import unquote_path
|
from ..utils import unquote_path
|
||||||
from .errors import UserError
|
from .errors import UserError
|
||||||
from .utils import generate_user_agent
|
from .utils import generate_user_agent
|
||||||
|
from .utils import get_version_info
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -24,6 +29,33 @@ def default_cert_path():
|
|||||||
return os.path.join(home_dir(), '.docker')
|
return os.path.join(home_dir(), '.docker')
|
||||||
|
|
||||||
|
|
||||||
|
def make_context(host, options, environment):
|
||||||
|
tls = tls_config_from_options(options, environment)
|
||||||
|
ctx = Context("compose", host=host)
|
||||||
|
if tls:
|
||||||
|
ctx.set_endpoint("docker", host, tls, skip_tls_verify=not tls.verify)
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
def load_context(name=None):
|
||||||
|
return ContextAPI.get_context(name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client(environment, verbose=False, version=None, context=None):
|
||||||
|
client = docker_client(
|
||||||
|
version=version, context=context,
|
||||||
|
environment=environment, tls_version=get_tls_version(environment)
|
||||||
|
)
|
||||||
|
if verbose:
|
||||||
|
version_info = six.iteritems(client.version())
|
||||||
|
log.info(get_version_info('full'))
|
||||||
|
log.info("Docker base_url: %s", client.base_url)
|
||||||
|
log.info("Docker version: %s",
|
||||||
|
", ".join("%s=%s" % item for item in version_info))
|
||||||
|
return verbose_proxy.VerboseProxy('docker', client)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
def get_tls_version(environment):
|
def get_tls_version(environment):
|
||||||
compose_tls_version = environment.get('COMPOSE_TLS_VERSION', None)
|
compose_tls_version = environment.get('COMPOSE_TLS_VERSION', None)
|
||||||
if not compose_tls_version:
|
if not compose_tls_version:
|
||||||
@ -87,8 +119,7 @@ def tls_config_from_options(options, environment=None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def docker_client(environment, version=None, tls_config=None, host=None,
|
def docker_client(environment, version=None, context=None, tls_version=None):
|
||||||
tls_version=None):
|
|
||||||
"""
|
"""
|
||||||
Returns a docker-py client configured using environment variables
|
Returns a docker-py client configured using environment variables
|
||||||
according to the same logic as the official Docker client.
|
according to the same logic as the official Docker client.
|
||||||
@ -101,10 +132,21 @@ def docker_client(environment, version=None, tls_config=None, host=None,
|
|||||||
"and DOCKER_CERT_PATH are set correctly.\n"
|
"and DOCKER_CERT_PATH are set correctly.\n"
|
||||||
"You might need to run `eval \"$(docker-machine env default)\"`")
|
"You might need to run `eval \"$(docker-machine env default)\"`")
|
||||||
|
|
||||||
|
if not context:
|
||||||
|
# check env for DOCKER_HOST and certs path
|
||||||
|
host = kwargs.get("base_url", None)
|
||||||
|
tls = kwargs.get("tls", None)
|
||||||
|
verify = False if not tls else tls.verify
|
||||||
if host:
|
if host:
|
||||||
kwargs['base_url'] = host
|
context = Context("compose", host=host)
|
||||||
if tls_config:
|
else:
|
||||||
kwargs['tls'] = tls_config
|
context = ContextAPI.get_current_context()
|
||||||
|
if tls:
|
||||||
|
context.set_endpoint("docker", host=host, tls_cfg=tls, skip_tls_verify=not verify)
|
||||||
|
|
||||||
|
kwargs['base_url'] = context.Host
|
||||||
|
if context.TLSConfig:
|
||||||
|
kwargs['tls'] = context.TLSConfig
|
||||||
|
|
||||||
if version:
|
if version:
|
||||||
kwargs['version'] = version
|
kwargs['version'] = version
|
||||||
|
@ -192,6 +192,7 @@ class TopLevelCommand(object):
|
|||||||
(default: docker-compose.yml)
|
(default: docker-compose.yml)
|
||||||
-p, --project-name NAME Specify an alternate project name
|
-p, --project-name NAME Specify an alternate project name
|
||||||
(default: directory name)
|
(default: directory name)
|
||||||
|
-c, --context NAME Specify a context name
|
||||||
--verbose Show more output
|
--verbose Show more output
|
||||||
--log-level LEVEL Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
--log-level LEVEL Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||||
--no-ansi Do not print ANSI control characters
|
--no-ansi Do not print ANSI control characters
|
||||||
|
@ -4,7 +4,7 @@ cached-property==1.5.1
|
|||||||
certifi==2019.11.28
|
certifi==2019.11.28
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
colorama==0.4.3; sys_platform == 'win32'
|
colorama==0.4.3; sys_platform == 'win32'
|
||||||
docker==4.1.0
|
docker==4.2.0
|
||||||
docker-pycreds==0.4.0
|
docker-pycreds==0.4.0
|
||||||
dockerpty==0.4.1
|
dockerpty==0.4.1
|
||||||
docopt==0.6.2
|
docopt==0.6.2
|
||||||
|
48
tests/acceptance/context_test.py
Normal file
48
tests/acceptance/context_test.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from docker import ContextAPI
|
||||||
|
|
||||||
|
from tests.acceptance.cli_test import dispatch
|
||||||
|
|
||||||
|
|
||||||
|
class ContextTestCase(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.docker_dir = os.path.join(os.environ.get("HOME", "/tmp"), '.docker')
|
||||||
|
if not os.path.exists(cls.docker_dir):
|
||||||
|
os.makedirs(cls.docker_dir)
|
||||||
|
f = open(os.path.join(cls.docker_dir, "config.json"), "w")
|
||||||
|
f.write("{}")
|
||||||
|
f.close()
|
||||||
|
cls.docker_config = os.path.join(cls.docker_dir, "config.json")
|
||||||
|
os.environ['DOCKER_CONFIG'] = cls.docker_config
|
||||||
|
ContextAPI.create_context("testcontext", host="tcp://doesnotexist:8000")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
shutil.rmtree(cls.docker_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.base_dir = 'tests/fixtures/simple-composefile'
|
||||||
|
self.override_dir = None
|
||||||
|
|
||||||
|
def dispatch(self, options, project_options=None, returncode=0, stdin=None):
|
||||||
|
return dispatch(self.base_dir, options, project_options, returncode, stdin)
|
||||||
|
|
||||||
|
def test_help(self):
|
||||||
|
result = self.dispatch(['help'], returncode=0)
|
||||||
|
assert '-c, --context NAME' in result.stdout
|
||||||
|
|
||||||
|
def test_fail_on_both_host_and_context_opt(self):
|
||||||
|
result = self.dispatch(['-H', 'unix://', '-c', 'default', 'up'], returncode=1)
|
||||||
|
assert '-H, --host and -c, --context are mutually exclusive' in result.stderr
|
||||||
|
|
||||||
|
def test_fail_run_on_inexistent_context(self):
|
||||||
|
result = self.dispatch(['-c', 'testcontext', 'up', '-d'], returncode=1)
|
||||||
|
assert "Couldn't connect to Docker daemon" in result.stderr
|
Loading…
x
Reference in New Issue
Block a user