diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index f309e1ec6dc..611bbe7a33d 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -663,7 +663,16 @@ def get_local_image_digest(self, image_name: str) -> Optional[str]: str Image digest, including `sha256:` prefix """ - image_info = self.docker_client.images.get(image_name) + try: + image_info = self.docker_client.images.get(image_name) + except docker.errors.ImageNotFound: + # Base image not found locally, pull it for digest comparison + try: + self.docker_client.images.pull(image_name) + image_info = self.docker_client.images.get(image_name) + except docker.errors.APIError: + return None + try: full_digest: str = image_info.attrs.get("RepoDigests", [None])[0] return full_digest.split("@")[1] diff --git a/tests/unit/local/docker/test_lambda_image.py b/tests/unit/local/docker/test_lambda_image.py index 04c3e7ec408..a79242ba0b7 100644 --- a/tests/unit/local/docker/test_lambda_image.py +++ b/tests/unit/local/docker/test_lambda_image.py @@ -786,6 +786,42 @@ def test_get_local_image_digest(self): lambda_image = LambdaImage("layer_downloader", False, False, docker_client=docker_client_mock) self.assertEqual("sha256:local-digest", lambda_image.get_local_image_digest("image_name")) + def test_get_local_image_digest_pulls_missing_image(self): + """Test that get_local_image_digest pulls the image if it's not found locally""" + docker_client_mock = Mock() + + # First call to images.get raises ImageNotFound + # Second call (after pull) returns the image data + local_image_data = Mock( + attrs={ + "RepoDigests": ["image_name@sha256:pulled-digest"], + }, + ) + docker_client_mock.images.get.side_effect = [ImageNotFound("Image not found"), local_image_data] + docker_client_mock.images.pull.return_value = None + + lambda_image = LambdaImage("layer_downloader", False, False, docker_client=docker_client_mock) + result = lambda_image.get_local_image_digest("image_name") + + # Should pull the image and return its digest + docker_client_mock.images.pull.assert_called_once_with("image_name") + self.assertEqual("sha256:pulled-digest", result) + + def test_get_local_image_digest_handles_pull_failure(self): + """Test that get_local_image_digest returns None if pull fails""" + docker_client_mock = Mock() + + # First call raises ImageNotFound, pull also fails + docker_client_mock.images.get.side_effect = ImageNotFound("Image not found") + docker_client_mock.images.pull.side_effect = APIError("Pull failed") + + lambda_image = LambdaImage("layer_downloader", False, False, docker_client=docker_client_mock) + result = lambda_image.get_local_image_digest("image_name") + + # Should attempt to pull and return None on failure + docker_client_mock.images.pull.assert_called_once_with("image_name") + self.assertIsNone(result) + @parameterized.expand( [ ("same-digest", "same-digest", True),