Skip to content

Commit 6018da3

Browse files
authored
fix(core): make is_podman respect resolved docker host (#1048)
Follow-up to #1026 (auto-detect DOCKER_HOST from the current docker context). ## Problem `is_podman()` used a bare `docker.from_env()`, which ignores the host resolved by `get_docker_host()` and never sets `use_ssh_client` for SSH hosts. When a remote daemon is configured via a docker context (and `DOCKER_HOST` is not exported), `is_podman()` hit the local default socket, failed, and silently returned `False`. Two consequences: - Podman over a docker context was never detected, so Podman-specific normalisation (e.g. in the port tests) was skipped. - SSH-based remote daemons could not be queried at all (paramiko also fails under pytest stdin capture, which is why `DockerClient` passes `use_ssh_client=True`). ## Fix `is_podman()` now resolves the host the same way `DockerClient` does: it builds a client with the resolved `base_url` (and `use_ssh_client=True` for `ssh://`), and only falls back to `docker.from_env()` when no host is resolved. ## Tests - Pinned `get_docker_host` to `None` in the existing `test_is_podman` parametrisation so it is deterministic regardless of the docker context configured on the machine running it (it was silently passing only when no context was set). - Added `test_is_podman_uses_resolved_host` covering the resolved-host SSH branch. ## Verified manually Ran `tests/core/test_core_ports.py` and `tests/core/test_docker_client.py` against two remote backends over SSH: | Context | Backend | `is_podman()` | Port tests | | --- | --- | --- | --- | | remote Podman | Podman 5.4 | `True` (was `False`) | 26 passed (was 10 failed) | | remote Docker | Docker 29.4 | `False` | 26 passed | Also simulated the Docker Desktop `desktop-linux` context from #757: `get_docker_host()` returns the context socket and `is_podman()` queries that same socket, returning `False` as expected.
1 parent 7dee471 commit 6018da3

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

src/testcontainers/core/docker_client.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,9 +535,20 @@ def is_podman() -> bool:
535535
``pytest.mark.skipif`` decorators.
536536
"""
537537
try:
538-
# Use docker.from_env() directly rather than DockerClient() so we avoid
539-
# the constructor's side effects (DOCKER_HOST mutation, registry login).
540-
version = docker.from_env().version()
538+
# Build the client directly rather than via DockerClient() so we avoid
539+
# the constructor's side effects (registry login). We still resolve the
540+
# host the same way DockerClient does so that hosts coming from the
541+
# docker context (and SSH connections) are detected correctly.
542+
docker_host = get_docker_host()
543+
if docker_host:
544+
client_kwargs: dict[str, Any] = {"base_url": docker_host}
545+
if docker_host.startswith("ssh://"):
546+
# Mirror DockerClient: use the shell SSH client to avoid paramiko
547+
# failures under pytest stdin capture.
548+
client_kwargs["use_ssh_client"] = True
549+
version = docker.DockerClient(**client_kwargs).version()
550+
else:
551+
version = docker.from_env().version()
541552
except Exception as e:
542553
LOGGER.debug(f"is_podman: failed to query daemon version: {e}")
543554
return False

tests/core/test_docker_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ def test_is_podman(monkeypatch: pytest.MonkeyPatch, version: object, expected: b
373373
from testcontainers.core import docker_client as dc
374374

375375
dc.is_podman.cache_clear()
376+
# Force the no-host branch so the test is deterministic regardless of the
377+
# docker context configured on the machine running it.
378+
monkeypatch.setattr("testcontainers.core.docker_client.get_docker_host", lambda: None)
376379
mock_client = MagicMock()
377380
if isinstance(version, Exception):
378381
monkeypatch.setattr("testcontainers.core.docker_client.docker.from_env", MagicMock(side_effect=version))
@@ -389,6 +392,28 @@ def test_is_podman(monkeypatch: pytest.MonkeyPatch, version: object, expected: b
389392
dc.is_podman.cache_clear()
390393

391394

395+
def test_is_podman_uses_resolved_host(monkeypatch: pytest.MonkeyPatch) -> None:
396+
"""When a host is resolved (e.g. from the docker context), is_podman must
397+
query that host rather than falling back to docker.from_env defaults."""
398+
from testcontainers.core import docker_client as dc
399+
400+
dc.is_podman.cache_clear()
401+
monkeypatch.setattr("testcontainers.core.docker_client.get_docker_host", lambda: "ssh://root@remote-podman")
402+
mock_client = MagicMock()
403+
mock_client.version.return_value = {"Platform": {"Name": "Podman Engine"}}
404+
mock_docker_client = MagicMock(return_value=mock_client)
405+
monkeypatch.setattr("testcontainers.core.docker_client.docker.DockerClient", mock_docker_client)
406+
monkeypatch.setattr(
407+
"testcontainers.core.docker_client.docker.from_env",
408+
MagicMock(side_effect=AssertionError("should not call from_env when host resolved")),
409+
)
410+
try:
411+
assert dc.is_podman() is True
412+
mock_docker_client.assert_called_once_with(base_url="ssh://root@remote-podman", use_ssh_client=True)
413+
finally:
414+
dc.is_podman.cache_clear()
415+
416+
392417
def _mock_docker_context(name: str, host: str) -> MagicMock:
393418
context = MagicMock()
394419
context.Name = name

0 commit comments

Comments
 (0)