From c1b9a76a54bf8bf4f8ab717768243410ee7d5169 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Tue, 7 Jul 2015 16:12:44 +0100 Subject: [PATCH] Merge pull request #1658 from aanand/fix-smart-recreate-nonexistent-image Fix smart recreate when 'image' is changed to something nonexistent (cherry picked from commit 2bc10db5451ec8e69119997061b0ba5c692feb90) 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 0db63e64f9..f1a006c326 100644 --- a/compose/service.py +++ b/compose/service.py @@ -64,6 +64,10 @@ class NeedsBuildError(Exception): self.service = service +class NoSuchImageError(Exception): + pass + + VolumeSpec = namedtuple('VolumeSpec', 'external internal mode') @@ -224,8 +228,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: @@ -240,7 +247,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 @@ -294,7 +301,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 7a7d2b58fc..6e1ba025b0 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -191,6 +191,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 88d3014702..dc1f7df34f 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, @@ -233,7 +234,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) @@ -273,7 +274,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() @@ -283,7 +284,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) @@ -298,7 +299,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) @@ -315,6 +316,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):