docker_client.py 5.6 KB

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