docker_client_test.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import os
  2. import platform
  3. import ssl
  4. import docker
  5. import pytest
  6. import compose
  7. from compose import const
  8. from compose.cli import errors
  9. from compose.cli.docker_client import docker_client
  10. from compose.cli.docker_client import get_tls_version
  11. from compose.cli.docker_client import tls_config_from_options
  12. from compose.config.environment import Environment
  13. from tests import mock
  14. from tests import unittest
  15. class DockerClientTestCase(unittest.TestCase):
  16. def test_docker_client_no_home(self):
  17. with mock.patch.dict(os.environ):
  18. try:
  19. del os.environ['HOME']
  20. except KeyError:
  21. pass
  22. docker_client(os.environ, version=const.API_VERSIONS[const.COMPOSE_SPEC])
  23. @mock.patch.dict(os.environ)
  24. def test_docker_client_with_custom_timeout(self):
  25. os.environ['COMPOSE_HTTP_TIMEOUT'] = '123'
  26. client = docker_client(os.environ, version=const.API_VERSIONS[const.COMPOSE_SPEC])
  27. assert client.timeout == 123
  28. @mock.patch.dict(os.environ)
  29. def test_custom_timeout_error(self):
  30. os.environ['COMPOSE_HTTP_TIMEOUT'] = '123'
  31. client = docker_client(os.environ, version=const.API_VERSIONS[const.COMPOSE_SPEC])
  32. with mock.patch('compose.cli.errors.log') as fake_log:
  33. with pytest.raises(errors.ConnectionError):
  34. with errors.handle_connection_errors(client):
  35. raise errors.RequestsConnectionError(
  36. errors.ReadTimeoutError(None, None, None))
  37. assert fake_log.error.call_count == 1
  38. assert '123' in fake_log.error.call_args[0][0]
  39. with mock.patch('compose.cli.errors.log') as fake_log:
  40. with pytest.raises(errors.ConnectionError):
  41. with errors.handle_connection_errors(client):
  42. raise errors.ReadTimeout()
  43. assert fake_log.error.call_count == 1
  44. assert '123' in fake_log.error.call_args[0][0]
  45. def test_user_agent(self):
  46. client = docker_client(os.environ, version=const.API_VERSIONS[const.COMPOSE_SPEC])
  47. expected = "docker-compose/{} docker-py/{} {}/{}".format(
  48. compose.__version__,
  49. docker.__version__,
  50. platform.system(),
  51. platform.release()
  52. )
  53. assert client.headers['User-Agent'] == expected
  54. class TLSConfigTestCase(unittest.TestCase):
  55. cert_path = 'tests/fixtures/tls/'
  56. ca_cert = os.path.join(cert_path, 'ca.pem')
  57. client_cert = os.path.join(cert_path, 'cert.pem')
  58. key = os.path.join(cert_path, 'key.pem')
  59. def test_simple_tls(self):
  60. options = {'--tls': True}
  61. result = tls_config_from_options(options)
  62. assert result is True
  63. def test_tls_ca_cert(self):
  64. options = {
  65. '--tlscacert': self.ca_cert, '--tlsverify': True
  66. }
  67. result = tls_config_from_options(options)
  68. assert isinstance(result, docker.tls.TLSConfig)
  69. assert result.ca_cert == options['--tlscacert']
  70. assert result.verify is True
  71. def test_tls_ca_cert_explicit(self):
  72. options = {
  73. '--tlscacert': self.ca_cert, '--tls': True,
  74. '--tlsverify': True
  75. }
  76. result = tls_config_from_options(options)
  77. assert isinstance(result, docker.tls.TLSConfig)
  78. assert result.ca_cert == options['--tlscacert']
  79. assert result.verify is True
  80. def test_tls_client_cert(self):
  81. options = {
  82. '--tlscert': self.client_cert, '--tlskey': self.key
  83. }
  84. result = tls_config_from_options(options)
  85. assert isinstance(result, docker.tls.TLSConfig)
  86. assert result.cert == (options['--tlscert'], options['--tlskey'])
  87. def test_tls_client_cert_explicit(self):
  88. options = {
  89. '--tlscert': self.client_cert, '--tlskey': self.key,
  90. '--tls': True
  91. }
  92. result = tls_config_from_options(options)
  93. assert isinstance(result, docker.tls.TLSConfig)
  94. assert result.cert == (options['--tlscert'], options['--tlskey'])
  95. def test_tls_client_and_ca(self):
  96. options = {
  97. '--tlscert': self.client_cert, '--tlskey': self.key,
  98. '--tlsverify': True, '--tlscacert': self.ca_cert
  99. }
  100. result = tls_config_from_options(options)
  101. assert isinstance(result, docker.tls.TLSConfig)
  102. assert result.cert == (options['--tlscert'], options['--tlskey'])
  103. assert result.ca_cert == options['--tlscacert']
  104. assert result.verify is True
  105. def test_tls_client_and_ca_explicit(self):
  106. options = {
  107. '--tlscert': self.client_cert, '--tlskey': self.key,
  108. '--tlsverify': True, '--tlscacert': self.ca_cert,
  109. '--tls': True
  110. }
  111. result = tls_config_from_options(options)
  112. assert isinstance(result, docker.tls.TLSConfig)
  113. assert result.cert == (options['--tlscert'], options['--tlskey'])
  114. assert result.ca_cert == options['--tlscacert']
  115. assert result.verify is True
  116. def test_tls_client_missing_key(self):
  117. options = {'--tlscert': self.client_cert}
  118. with pytest.raises(docker.errors.TLSParameterError):
  119. tls_config_from_options(options)
  120. options = {'--tlskey': self.key}
  121. with pytest.raises(docker.errors.TLSParameterError):
  122. tls_config_from_options(options)
  123. def test_assert_hostname_explicit_skip(self):
  124. options = {'--tlscacert': self.ca_cert, '--skip-hostname-check': True}
  125. result = tls_config_from_options(options)
  126. assert isinstance(result, docker.tls.TLSConfig)
  127. assert result.assert_hostname is False
  128. def test_tls_client_and_ca_quoted_paths(self):
  129. options = {
  130. '--tlscacert': '"{}"'.format(self.ca_cert),
  131. '--tlscert': '"{}"'.format(self.client_cert),
  132. '--tlskey': '"{}"'.format(self.key),
  133. '--tlsverify': True
  134. }
  135. result = tls_config_from_options(options)
  136. assert isinstance(result, docker.tls.TLSConfig)
  137. assert result.cert == (self.client_cert, self.key)
  138. assert result.ca_cert == self.ca_cert
  139. assert result.verify is True
  140. def test_tls_simple_with_tls_version(self):
  141. tls_version = 'TLSv1'
  142. options = {'--tls': True}
  143. environment = Environment({'COMPOSE_TLS_VERSION': tls_version})
  144. result = tls_config_from_options(options, environment)
  145. assert isinstance(result, docker.tls.TLSConfig)
  146. assert result.ssl_version == ssl.PROTOCOL_TLSv1
  147. def test_tls_mixed_environment_and_flags(self):
  148. options = {'--tls': True, '--tlsverify': False}
  149. environment = Environment({'DOCKER_CERT_PATH': 'tests/fixtures/tls/'})
  150. result = tls_config_from_options(options, environment)
  151. assert isinstance(result, docker.tls.TLSConfig)
  152. assert result.cert == (self.client_cert, self.key)
  153. assert result.ca_cert == self.ca_cert
  154. assert result.verify is False
  155. def test_tls_flags_override_environment(self):
  156. environment = Environment({
  157. 'DOCKER_CERT_PATH': '/completely/wrong/path',
  158. 'DOCKER_TLS_VERIFY': 'false'
  159. })
  160. options = {
  161. '--tlscacert': '"{}"'.format(self.ca_cert),
  162. '--tlscert': '"{}"'.format(self.client_cert),
  163. '--tlskey': '"{}"'.format(self.key),
  164. '--tlsverify': True
  165. }
  166. result = tls_config_from_options(options, environment)
  167. assert isinstance(result, docker.tls.TLSConfig)
  168. assert result.cert == (self.client_cert, self.key)
  169. assert result.ca_cert == self.ca_cert
  170. assert result.verify is True
  171. def test_tls_verify_flag_no_override(self):
  172. environment = Environment({
  173. 'DOCKER_TLS_VERIFY': 'true',
  174. 'COMPOSE_TLS_VERSION': 'TLSv1',
  175. 'DOCKER_CERT_PATH': self.cert_path
  176. })
  177. options = {'--tls': True, '--tlsverify': False}
  178. result = tls_config_from_options(options, environment)
  179. assert isinstance(result, docker.tls.TLSConfig)
  180. assert result.ssl_version == ssl.PROTOCOL_TLSv1
  181. # verify is a special case - since `--tlsverify` = False means it
  182. # wasn't used, we set it if either the environment or the flag is True
  183. # see https://github.com/docker/compose/issues/5632
  184. assert result.verify is True
  185. def test_tls_verify_env_falsy_value(self):
  186. environment = Environment({'DOCKER_TLS_VERIFY': '0'})
  187. options = {'--tls': True}
  188. assert tls_config_from_options(options, environment) is True
  189. def test_tls_verify_default_cert_path(self):
  190. environment = Environment({'DOCKER_TLS_VERIFY': '1'})
  191. options = {'--tls': True}
  192. with mock.patch('compose.cli.docker_client.default_cert_path') as dcp:
  193. dcp.return_value = 'tests/fixtures/tls/'
  194. result = tls_config_from_options(options, environment)
  195. assert isinstance(result, docker.tls.TLSConfig)
  196. assert result.verify is True
  197. assert result.ca_cert == self.ca_cert
  198. assert result.cert == (self.client_cert, self.key)
  199. class TestGetTlsVersion:
  200. def test_get_tls_version_default(self):
  201. environment = {}
  202. assert get_tls_version(environment) is None
  203. @pytest.mark.skipif(not hasattr(ssl, 'PROTOCOL_TLSv1_2'), reason='TLS v1.2 unsupported')
  204. def test_get_tls_version_upgrade(self):
  205. environment = {'COMPOSE_TLS_VERSION': 'TLSv1_2'}
  206. assert get_tls_version(environment) == ssl.PROTOCOL_TLSv1_2
  207. def test_get_tls_version_unavailable(self):
  208. environment = {'COMPOSE_TLS_VERSION': 'TLSv5_5'}
  209. with mock.patch('compose.cli.docker_client.log') as mock_log:
  210. tls_version = get_tls_version(environment)
  211. mock_log.warning.assert_called_once_with(mock.ANY)
  212. assert tls_version is None