bundle_test.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. from __future__ import absolute_import
  2. from __future__ import unicode_literals
  3. import docker
  4. import pytest
  5. from .. import mock
  6. from compose import bundle
  7. from compose import service
  8. from compose.cli.errors import UserError
  9. from compose.config.config import Config
  10. from compose.const import COMPOSEFILE_V2_0 as V2_0
  11. from compose.service import NoSuchImageError
  12. @pytest.fixture
  13. def mock_service():
  14. return mock.create_autospec(
  15. service.Service,
  16. client=mock.create_autospec(docker.APIClient),
  17. options={})
  18. def test_get_image_digest_exists(mock_service):
  19. mock_service.options['image'] = 'abcd'
  20. mock_service.image.return_value = {'RepoDigests': ['digest1']}
  21. digest = bundle.get_image_digest(mock_service)
  22. assert digest == 'digest1'
  23. def test_get_image_digest_image_uses_digest(mock_service):
  24. mock_service.options['image'] = image_id = 'redis@sha256:digest'
  25. digest = bundle.get_image_digest(mock_service)
  26. assert digest == image_id
  27. assert not mock_service.image.called
  28. def test_get_image_digest_from_repository(mock_service):
  29. mock_service.options['image'] = 'abcd'
  30. mock_service.image_name = 'abcd'
  31. mock_service.image.side_effect = NoSuchImageError(None)
  32. mock_service.get_image_registry_data.return_value = {'Descriptor': {'digest': 'digest'}}
  33. digest = bundle.get_image_digest(mock_service)
  34. assert digest == 'abcd@digest'
  35. def test_get_image_digest_no_image(mock_service):
  36. with pytest.raises(UserError) as exc:
  37. bundle.get_image_digest(service.Service(name='theservice'))
  38. assert "doesn't define an image tag" in exc.exconly()
  39. def test_push_image_with_saved_digest(mock_service):
  40. mock_service.options['build'] = '.'
  41. mock_service.options['image'] = image_id = 'abcd'
  42. mock_service.push.return_value = expected = 'sha256:thedigest'
  43. mock_service.image.return_value = {'RepoDigests': ['digest1']}
  44. digest = bundle.push_image(mock_service)
  45. assert digest == image_id + '@' + expected
  46. mock_service.push.assert_called_once_with()
  47. assert not mock_service.client.push.called
  48. def test_push_image(mock_service):
  49. mock_service.options['build'] = '.'
  50. mock_service.options['image'] = image_id = 'abcd'
  51. mock_service.push.return_value = expected = 'sha256:thedigest'
  52. mock_service.image.return_value = {'RepoDigests': []}
  53. digest = bundle.push_image(mock_service)
  54. assert digest == image_id + '@' + expected
  55. mock_service.push.assert_called_once_with()
  56. mock_service.client.pull.assert_called_once_with(digest)
  57. def test_to_bundle():
  58. image_digests = {'a': 'aaaa', 'b': 'bbbb'}
  59. services = [
  60. {'name': 'a', 'build': '.', },
  61. {'name': 'b', 'build': './b'},
  62. ]
  63. config = Config(
  64. version=V2_0,
  65. services=services,
  66. volumes={'special': {}},
  67. networks={'extra': {}},
  68. secrets={},
  69. configs={}
  70. )
  71. with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
  72. output = bundle.to_bundle(config, image_digests)
  73. assert mock_log.mock_calls == [
  74. mock.call("Unsupported top level key 'networks' - ignoring"),
  75. mock.call("Unsupported top level key 'volumes' - ignoring"),
  76. ]
  77. assert output == {
  78. 'Version': '0.1',
  79. 'Services': {
  80. 'a': {'Image': 'aaaa', 'Networks': ['default']},
  81. 'b': {'Image': 'bbbb', 'Networks': ['default']},
  82. }
  83. }
  84. def test_convert_service_to_bundle():
  85. name = 'theservice'
  86. image_digest = 'thedigest'
  87. service_dict = {
  88. 'ports': ['80'],
  89. 'expose': ['1234'],
  90. 'networks': {'extra': {}},
  91. 'command': 'foo',
  92. 'entrypoint': 'entry',
  93. 'environment': {'BAZ': 'ENV'},
  94. 'build': '.',
  95. 'working_dir': '/tmp',
  96. 'user': 'root',
  97. 'labels': {'FOO': 'LABEL'},
  98. 'privileged': True,
  99. }
  100. with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
  101. config = bundle.convert_service_to_bundle(name, service_dict, image_digest)
  102. mock_log.assert_called_once_with(
  103. "Unsupported key 'privileged' in services.theservice - ignoring")
  104. assert config == {
  105. 'Image': image_digest,
  106. 'Ports': [
  107. {'Protocol': 'tcp', 'Port': 80},
  108. {'Protocol': 'tcp', 'Port': 1234},
  109. ],
  110. 'Networks': ['extra'],
  111. 'Command': ['entry', 'foo'],
  112. 'Env': ['BAZ=ENV'],
  113. 'WorkingDir': '/tmp',
  114. 'User': 'root',
  115. 'Labels': {'FOO': 'LABEL'},
  116. }
  117. def test_set_command_and_args_none():
  118. config = {}
  119. bundle.set_command_and_args(config, [], [])
  120. assert config == {}
  121. def test_set_command_and_args_from_command():
  122. config = {}
  123. bundle.set_command_and_args(config, [], "echo ok")
  124. assert config == {'Args': ['echo', 'ok']}
  125. def test_set_command_and_args_from_entrypoint():
  126. config = {}
  127. bundle.set_command_and_args(config, "echo entry", [])
  128. assert config == {'Command': ['echo', 'entry']}
  129. def test_set_command_and_args_from_both():
  130. config = {}
  131. bundle.set_command_and_args(config, "echo entry", ["extra", "arg"])
  132. assert config == {'Command': ['echo', 'entry', "extra", "arg"]}
  133. def test_make_service_networks_default():
  134. name = 'theservice'
  135. service_dict = {}
  136. with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
  137. networks = bundle.make_service_networks(name, service_dict)
  138. assert not mock_log.called
  139. assert networks == ['default']
  140. def test_make_service_networks():
  141. name = 'theservice'
  142. service_dict = {
  143. 'networks': {
  144. 'foo': {
  145. 'aliases': ['one', 'two'],
  146. },
  147. 'bar': {}
  148. },
  149. }
  150. with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log:
  151. networks = bundle.make_service_networks(name, service_dict)
  152. mock_log.assert_called_once_with(
  153. "Unsupported key 'aliases' in services.theservice.networks.foo - ignoring")
  154. assert sorted(networks) == sorted(service_dict['networks'])
  155. def test_make_port_specs():
  156. service_dict = {
  157. 'expose': ['80', '500/udp'],
  158. 'ports': [
  159. '400:80',
  160. '222',
  161. '127.0.0.1:8001:8001',
  162. '127.0.0.1:5000-5001:3000-3001'],
  163. }
  164. port_specs = bundle.make_port_specs(service_dict)
  165. assert port_specs == [
  166. {'Protocol': 'tcp', 'Port': 80},
  167. {'Protocol': 'tcp', 'Port': 222},
  168. {'Protocol': 'tcp', 'Port': 8001},
  169. {'Protocol': 'tcp', 'Port': 3000},
  170. {'Protocol': 'tcp', 'Port': 3001},
  171. {'Protocol': 'udp', 'Port': 500},
  172. ]
  173. def test_make_port_spec_with_protocol():
  174. port_spec = bundle.make_port_spec("5000/udp")
  175. assert port_spec == {'Protocol': 'udp', 'Port': 5000}
  176. def test_make_port_spec_default_protocol():
  177. port_spec = bundle.make_port_spec("50000")
  178. assert port_spec == {'Protocol': 'tcp', 'Port': 50000}