bundle_test.py 6.8 KB

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