From adf67135b702641fa3afa07e94fe4342e5b5890b Mon Sep 17 00:00:00 2001 From: uditDewan Date: Sun, 2 Aug 2026 17:23:38 -0400 Subject: [PATCH] Fix Container.image resolving the wrong image `Container.image` assumed the value it looked up was always a digest and split it on the first colon. That is only true for `ImageID`. Daemons that omit `ImageID` report an image name in `Image` instead, so `busybox:latest` was split into `latest` and looked up as if it were an image ID, raising `ImageNotFound` (or silently returning an unrelated image that happens to be tagged `latest`). Strip only the `sha256:` digest prefix and pass anything else through untouched, matching how `Image.short_id` already treats these values. Also read `Image` with `.get()`. It was passed as the default argument to `self.attrs.get('ImageID', ...)`, which Python evaluates eagerly, so a container whose attributes had `ImageID` but no `Image` raised `KeyError` instead of using the `ImageID` that was present. Fixes #3144 Signed-off-by: uditDewan --- docker/models/containers.py | 9 +++++++-- tests/unit/models_containers_test.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 9c9e92c90f..4a7e9c1faf 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -38,10 +38,15 @@ def image(self): """ The image of the container. """ - image_id = self.attrs.get('ImageID', self.attrs['Image']) + image_id = self.attrs.get('ImageID') or self.attrs.get('Image') if image_id is None: return None - return self.client.images.get(image_id.split(':')[1]) + # ``ImageID`` is a digest; strip the algorithm prefix from it. Daemons + # that omit ``ImageID`` report a name such as ``busybox:latest`` in + # ``Image`` instead, which must not be split on its tag separator. + if image_id.startswith('sha256:'): + image_id = image_id[len('sha256:'):] + return self.client.images.get(image_id) @property def labels(self): diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 0e2ae341a9..a2d14dcaae 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -725,6 +725,24 @@ def test_image(self): client = make_fake_client() container = client.containers.get(FAKE_CONTAINER_ID) assert container.image.id == FAKE_IMAGE_ID + # The container reports no 'ImageID', so the name in 'Image' is looked + # up as-is rather than being split on its tag separator. + client.api.inspect_image.assert_called_with('busybox:latest') + + def test_image_from_image_id(self): + client = make_fake_client() + container = client.containers.get(FAKE_CONTAINER_ID) + container.attrs['ImageID'] = FAKE_IMAGE_ID + assert container.image.id == FAKE_IMAGE_ID + client.api.inspect_image.assert_called_with( + FAKE_IMAGE_ID[len('sha256:'):] + ) + + def test_image_without_image_id_or_image(self): + client = make_fake_client() + container = client.containers.get(FAKE_CONTAINER_ID) + del container.attrs['Image'] + assert container.image is None def test_kill(self): client = make_fake_client()