Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion podman/domain/containers_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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"),
Expand Down
44 changes: 44 additions & 0 deletions podman/tests/unit/test_containersmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Loading