diff --git a/compose/service.py b/compose/service.py index 0db63e64f..f1a006c32 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 7a7d2b58f..6e1ba025b 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 88d301470..dc1f7df34 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):