From 531265bc8421ca17d91099f38d54aa7cf91d777e Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 6 Jul 2015 15:30:27 +0100 Subject: [PATCH] Fix smart recreate when 'image' is changed to something nonexistent Signed-off-by: Aanand Prasad --- compose/service.py | 23 ++++++++++++++++++++--- tests/integration/state_test.py | 7 +++++++ tests/unit/service_test.py | 16 ++++++++++++---- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/compose/service.py b/compose/service.py index 6fec794fa..0793f6620 100644 --- a/compose/service.py +++ b/compose/service.py @@ -65,6 +65,10 @@ class NeedsBuildError(Exception): self.service = service +class NoSuchImageError(Exception): + pass + + VolumeSpec = namedtuple('VolumeSpec', 'external internal mode') @@ -225,8 +229,11 @@ class Service(object): do_build=True, insecure_registry=False): - if self.image(): + try: + self.image() return + except NoSuchImageError: + pass if self.can_be_built(): if do_build: @@ -241,7 +248,7 @@ class Service(object): return self.client.inspect_image(self.image_name) except APIError as e: if e.response.status_code == 404 and e.explanation and 'No such image' in str(e.explanation): - return None + raise NoSuchImageError("Image '{}' not found".format(self.image_name)) else: raise @@ -275,7 +282,17 @@ class Service(object): return ConvergencePlan('recreate', containers) def _containers_have_diverged(self, containers): - config_hash = self.config_hash() + config_hash = None + + try: + config_hash = self.config_hash() + except NoSuchImageError as e: + log.debug( + 'Service %s has diverged: %s', + self.name, six.text_type(e), + ) + return True + has_diverged = False for c in containers: diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py index 68c7b0c67..d36e63207 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -215,6 +215,13 @@ class ServiceStateTest(DockerClientTestCase): web = self.create_service('web', command=["top", "-d", "1"]) self.assertEqual(('recreate', [container]), web.convergence_plan(smart_recreate=True)) + def test_trigger_recreate_with_nonexistent_image_tag(self): + web = self.create_service('web', image="busybox:latest") + container = web.create_container() + + web = self.create_service('web', image="nonexistent-image") + self.assertEqual(('recreate', [container]), web.convergence_plan(smart_recreate=True)) + def test_trigger_recreate_with_image_change(self): repo = 'composetest_myimage' tag = 'latest' diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index d36937150..818cb0f78 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -12,6 +12,7 @@ from compose.const import LABEL_SERVICE, LABEL_PROJECT, LABEL_ONE_OFF from compose.service import ( ConfigError, NeedsBuildError, + NoSuchImageError, build_port_bindings, build_volume_binding, get_container_data_volumes, @@ -245,7 +246,7 @@ class ServiceTest(unittest.TestCase): images.append({'Id': 'abc123'}) return [] - service.image = lambda: images[0] if images else None + service.image = lambda *args, **kwargs: mock_get_image(images) self.mock_client.pull = pull service.create_container(insecure_registry=True) @@ -294,7 +295,7 @@ class ServiceTest(unittest.TestCase): images.append({'Id': 'abc123'}) return [] - service.image = lambda: images[0] if images else None + service.image = lambda *args, **kwargs: mock_get_image(images) self.mock_client.pull = pull service.create_container() @@ -304,7 +305,7 @@ class ServiceTest(unittest.TestCase): service = Service('foo', client=self.mock_client, build='.') images = [] - service.image = lambda *args, **kwargs: images[0] if images else None + service.image = lambda *args, **kwargs: mock_get_image(images) service.build = lambda: images.append({'Id': 'abc123'}) service.create_container(do_build=True) @@ -319,7 +320,7 @@ class ServiceTest(unittest.TestCase): def test_create_container_no_build_but_needs_build(self): service = Service('foo', client=self.mock_client, build='.') - service.image = lambda: None + service.image = lambda *args, **kwargs: mock_get_image([]) with self.assertRaises(NeedsBuildError): service.create_container(do_build=False) @@ -336,6 +337,13 @@ class ServiceTest(unittest.TestCase): self.assertFalse(self.mock_client.build.call_args[1]['pull']) +def mock_get_image(images): + if images: + return images[0] + else: + raise NoSuchImageError() + + class ServiceVolumesTest(unittest.TestCase): def setUp(self):