docker_client.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import logging
  4. import os.path
  5. import ssl
  6. import six
  7. from docker import APIClient
  8. from docker import Context
  9. from docker import ContextAPI
  10. from docker import TLSConfig
  11. from docker.errors import TLSParameterError
  12. from docker.utils import kwargs_from_env
  13. from docker.utils.config import home_dir
  14. from . import verbose_proxy
  15. from ..config.environment import Environment
  16. from ..const import HTTP_TIMEOUT
  17. from ..utils import unquote_path
  18. from .errors import UserError
  19. from .utils import generate_user_agent
  20. from .utils import get_version_info
  21. log = logging.getLogger(__name__)
  22. def default_cert_path():
  23. return os.path.join(home_dir(), '.docker')
  24. def make_context(host, options, environment):
  25. tls = tls_config_from_options(options, environment)
  26. ctx = Context("compose", host=host, tls=tls.verify if tls else False)
  27. if tls:
  28. ctx.set_endpoint("docker", host, tls, skip_tls_verify=not tls.verify)
  29. return ctx
  30. def load_context(name=None):
  31. return ContextAPI.get_context(name)
  32. def get_client(environment, verbose=False, version=None, context=None):
  33. client = docker_client(
  34. version=version, context=context,
  35. environment=environment, tls_version=get_tls_version(environment)
  36. )
  37. if verbose:
  38. version_info = six.iteritems(client.version())
  39. log.info(get_version_info('full'))
  40. log.info("Docker base_url: %s", client.base_url)
  41. log.info("Docker version: %s",
  42. ", ".join("%s=%s" % item for item in version_info))
  43. return verbose_proxy.VerboseProxy('docker', client)
  44. return client
  45. def get_tls_version(environment):
  46. compose_tls_version = environment.get('COMPOSE_TLS_VERSION', None)
  47. if not compose_tls_version:
  48. return None
  49. tls_attr_name = "PROTOCOL_{}".format(compose_tls_version)
  50. if not hasattr(ssl, tls_attr_name):
  51. log.warning(
  52. 'The "{}" protocol is unavailable. You may need to update your '
  53. 'version of Python or OpenSSL. Falling back to TLSv1 (default).'
  54. .format(compose_tls_version)
  55. )
  56. return None
  57. return getattr(ssl, tls_attr_name)
  58. def tls_config_from_options(options, environment=None):
  59. environment = environment or Environment()
  60. cert_path = environment.get('DOCKER_CERT_PATH') or None
  61. tls = options.get('--tls', False)
  62. ca_cert = unquote_path(options.get('--tlscacert'))
  63. cert = unquote_path(options.get('--tlscert'))
  64. key = unquote_path(options.get('--tlskey'))
  65. # verify is a special case - with docopt `--tlsverify` = False means it
  66. # wasn't used, so we set it if either the environment or the flag is True
  67. # see https://github.com/docker/compose/issues/5632
  68. verify = options.get('--tlsverify') or environment.get_boolean('DOCKER_TLS_VERIFY')
  69. skip_hostname_check = options.get('--skip-hostname-check', False)
  70. if cert_path is not None and not any((ca_cert, cert, key)):
  71. # FIXME: Modify TLSConfig to take a cert_path argument and do this internally
  72. cert = os.path.join(cert_path, 'cert.pem')
  73. key = os.path.join(cert_path, 'key.pem')
  74. ca_cert = os.path.join(cert_path, 'ca.pem')
  75. if verify and not any((ca_cert, cert, key)):
  76. # Default location for cert files is ~/.docker
  77. ca_cert = os.path.join(default_cert_path(), 'ca.pem')
  78. cert = os.path.join(default_cert_path(), 'cert.pem')
  79. key = os.path.join(default_cert_path(), 'key.pem')
  80. tls_version = get_tls_version(environment)
  81. advanced_opts = any([ca_cert, cert, key, verify, tls_version])
  82. if tls is True and not advanced_opts:
  83. return True
  84. elif advanced_opts: # --tls is a noop
  85. client_cert = None
  86. if cert or key:
  87. client_cert = (cert, key)
  88. return TLSConfig(
  89. client_cert=client_cert, verify=verify, ca_cert=ca_cert,
  90. assert_hostname=False if skip_hostname_check else None,
  91. ssl_version=tls_version
  92. )
  93. return None
  94. def docker_client(environment, version=None, context=None, tls_version=None):
  95. """
  96. Returns a docker-py client configured using environment variables
  97. according to the same logic as the official Docker client.
  98. """
  99. try:
  100. kwargs = kwargs_from_env(environment=environment, ssl_version=tls_version)
  101. except TLSParameterError:
  102. raise UserError(
  103. "TLS configuration is invalid - make sure your DOCKER_TLS_VERIFY "
  104. "and DOCKER_CERT_PATH are set correctly.\n"
  105. "You might need to run `eval \"$(docker-machine env default)\"`")
  106. if not context:
  107. # check env for DOCKER_HOST and certs path
  108. host = kwargs.get("base_url", None)
  109. tls = kwargs.get("tls", None)
  110. verify = False if not tls else tls.verify
  111. if host:
  112. context = Context("compose", host=host, tls=verify)
  113. else:
  114. context = ContextAPI.get_current_context()
  115. if tls:
  116. context.set_endpoint("docker", host=host, tls_cfg=tls, skip_tls_verify=not verify)
  117. kwargs['base_url'] = context.Host
  118. if context.TLSConfig:
  119. kwargs['tls'] = context.TLSConfig
  120. if version:
  121. kwargs['version'] = version
  122. timeout = environment.get('COMPOSE_HTTP_TIMEOUT')
  123. if timeout:
  124. kwargs['timeout'] = int(timeout)
  125. else:
  126. kwargs['timeout'] = HTTP_TIMEOUT
  127. kwargs['user_agent'] = generate_user_agent()
  128. # Workaround for
  129. # https://pyinstaller.readthedocs.io/en/v3.3.1/runtime-information.html#ld-library-path-libpath-considerations
  130. if 'LD_LIBRARY_PATH_ORIG' in environment:
  131. kwargs['credstore_env'] = {
  132. 'LD_LIBRARY_PATH': environment.get('LD_LIBRARY_PATH_ORIG'),
  133. }
  134. client = APIClient(**kwargs)
  135. client._original_base_url = kwargs.get('base_url')
  136. return client