diff --git a/podman/domain/containers_run.py b/podman/domain/containers_run.py index 7ab60f97..fee4d45d 100644 --- a/podman/domain/containers_run.py +++ b/podman/domain/containers_run.py @@ -8,7 +8,7 @@ from podman.domain.containers import Container from podman.domain.images import Image -from podman.errors import ContainerError, ImageNotFound +from podman.errors import APIError, ContainerError, ImageNotFound logger = logging.getLogger("podman.containers") @@ -72,9 +72,19 @@ def run( if isinstance(command, str): command = [command] + _needs_pull = False try: container = self.create(image=image_id, command=command, **kwargs) # type: ignore[attr-defined] except ImageNotFound: + _needs_pull = True + except APIError as e: + # Podman may return HTTP 500 with "image not known" instead of 404 + # when an image is missing locally; re-raise any unrelated API errors. + if "image not known" not in str(e): + raise + _needs_pull = True + + if _needs_pull: self.podman_client.images.pull( # type: ignore[attr-defined] image_id, auth_config=kwargs.get("auth_config"), diff --git a/podman/tests/unit/test_containersmanager.py b/podman/tests/unit/test_containersmanager.py index 92f58896..01083adc 100644 --- a/podman/tests/unit/test_containersmanager.py +++ b/podman/tests/unit/test_containersmanager.py @@ -644,6 +644,50 @@ def test_run(self, mock): self.assertEqual(next(actual), b"This is a unittest - line 1") self.assertEqual(next(actual), b"This is a unittest - line 2") + @requests_mock.Mocker() + def test_run_pulls_image_on_api_error_image_not_known(self, mock): + """run() should pull image and retry when Podman returns 500 'image not known'.""" + image_id = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + image_id_encoded = image_id.replace(":", "%3A") + container_id = FIRST_CONTAINER["Id"] + + mock.post( + tests.LIBPOD_URL + "/containers/create", + [ + { + "status_code": 500, + "json": {"cause": "image not known", "message": "fedora: image not known"}, + }, + { + "status_code": 201, + "json": {"Id": container_id, "Warnings": []}, + }, + ], + ) + mock.post( + tests.LIBPOD_URL + "/images/pull", + json={"error": "", "id": image_id, "images": [image_id], "stream": ""}, + ) + mock.get( + tests.LIBPOD_URL + f"/images/{image_id_encoded}/json", + json={"Id": image_id}, + ) + mock.post( + tests.LIBPOD_URL + f"/containers/{container_id}/start", + status_code=204, + ) + mock.get( + tests.LIBPOD_URL + f"/containers/{container_id}/json", + json=FIRST_CONTAINER, + ) + + with patch.multiple(Container, logs=DEFAULT, wait=DEFAULT, autospec=True) as mock_container: + mock_container["wait"].return_value = 0 + mock_container["logs"].return_value = iter([b"output"]) + + actual = self.client.containers.run("fedora", "/usr/bin/ls") + self.assertIsInstance(actual, bytes) + @requests_mock.Mocker() def test_create_all_healthcheck_parameters(self, mock): """Test that all healthcheck parameters are correctly passed to the API."""