Skip to content

Commit ff24dc0

Browse files
Merge pull request #914 from jiridanek/jd_refactor_sync_2
[main] NO-JIRA: chore(tests/containers): detect socket for ryuk's use with rootless podman on macOS
2 parents 3c581b5 + 2e68fd4 commit ff24dc0

File tree

10 files changed

+630
-55
lines changed

10 files changed

+630
-55
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock poetry run pytest tests/con
110110
# Mac OS
111111
brew install podman
112112
podman machine init
113-
podman machine set --rootful
113+
podman machine set --rootful=false
114114
sudo podman-mac-helper install
115115
podman machine start
116116
poetry run pytest tests/containers --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4

poetry.lock

Lines changed: 30 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pytest-subtests = "^0.14.1"
1717
pyfakefs = "^5.7.4"
1818
testcontainers = "^4.9.1"
1919
docker = "^7.1.0"
20+
podman = "^5.2.0"
2021
pydantic = "^2.10.6"
2122
requests = "^2.32.3"
2223

tests/containers/conftest.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import os
5+
import platform
56
from typing import Iterable, Callable, TYPE_CHECKING
67

78
import testcontainers.core.config
@@ -120,7 +121,16 @@ def pytest_sessionstart(session: Session) -> None:
120121

121122
# set that socket path for ryuk's use, unless user overrode that
122123
if TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE not in os.environ:
123-
testcontainers.core.config.testcontainers_config.ryuk_docker_socket = socket_path
124+
logging.info(f"Env variable TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE not set, setting it now")
125+
if platform.system().lower() == 'linux':
126+
logging.info(f"We are on Linux, setting {socket_path=} for TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")
127+
testcontainers.core.config.testcontainers_config.ryuk_docker_socket = socket_path
128+
elif platform.system().lower() == 'darwin':
129+
podman_machine_socket_path = docker_utils.get_podman_machine_socket_path(client.client)
130+
logging.info(f"We are on macOS, setting {podman_machine_socket_path=} for TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")
131+
testcontainers.core.config.testcontainers_config.ryuk_docker_socket = podman_machine_socket_path
132+
else:
133+
raise RuntimeError(f"Unsupported platform {platform.system()=}, cannot set TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")
124134

125135
# second preflight check: start the Reaper container
126136
if not testcontainers.core.config.testcontainers_config.ryuk_disabled:

tests/containers/docker_utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
import time
99
from typing import Iterable, TYPE_CHECKING
1010

11+
import podman
1112
import docker.client
1213

1314
import testcontainers.core.container
1415

16+
import tests.containers.pydantic_schemas
17+
1518
if TYPE_CHECKING:
1619
from docker.models.containers import Container
1720

@@ -158,13 +161,29 @@ def communicate(self, line_prefix=b"") -> int:
158161
raise RuntimeError("Hm could that really happen?")
159162
return self.poll()
160163

164+
161165
def get_socket_path(client: docker.client.DockerClient) -> str:
162166
"""Determine the local socket path.
163167
This works even when `podman machine` with its own host-mounts is involved
164168
NOTE: this will not work for remote docker, but we will cross the bridge when we come to it"""
165169
socket_path = _the_one(adapter.socket_path for adapter in client.api.adapters.values())
166170
return socket_path
167171

172+
173+
def get_podman_machine_socket_path(docker_client: docker.client.DockerClient) -> str:
174+
"""Determine the podman socket path that's valid from inside Podman Machine.
175+
* rootful podman: both the host (`ls`) and podman machine (`podman machine ssh ls`) have it at `/var/run/docker.sock`.
176+
* rootless podman: the location on host is still the same while podman machine has it in `/var/run/user/${PID}/podman/podman.sock`.
177+
"""
178+
socket_path = get_socket_path(docker_client)
179+
podman_client = podman.PodmanClient(base_url="http+unix://" + socket_path)
180+
info = tests.containers.pydantic_schemas.PodmanInfo.model_validate(podman_client.info())
181+
assert info.host.remoteSocket.exists, "Failed to determine the podman remote socket"
182+
assert info.host.remoteSocket.path.startswith("unix://"), "Unexpected remote socket path"
183+
machine_socket_path = info.host.remoteSocket.path[len("unix://"):]
184+
return machine_socket_path
185+
186+
168187
def get_container_pid(container: Container) -> int | None:
169188
"""Get the network namespace of a Docker container."""
170189
container.reload()

tests/containers/podman_machine_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import subprocess
55
from typing import Callable
66

7-
import tests.containers.schemas
7+
import tests.containers.pydantic_schemas
88

99
logging.basicConfig(level=logging.DEBUG)
1010

11-
def open_ssh_tunnel(machine_predicate: Callable[[tests.containers.schemas.PodmanMachine], bool],
11+
def open_ssh_tunnel(machine_predicate: Callable[[tests.containers.pydantic_schemas.PodmanMachine], bool],
1212
local_port: int, remote_port: int, remote_interface: str = "localhost") -> subprocess.Popen:
1313
# Load and parse the Podman machine data
1414
machine_names = subprocess.check_output(["podman", "machine", "list", "--quiet"], text=True).splitlines()
1515
json_data = subprocess.check_output(["podman", "machine", "inspect", *machine_names], text=True)
16-
inspect = tests.containers.schemas.PodmanMachineInspect(machines=json.loads(json_data))
16+
inspect = tests.containers.pydantic_schemas.PodmanMachineInspect(machines=json.loads(json_data))
1717
machines = inspect.machines
1818

1919
machine = next((m for m in machines if machine_predicate(m)), None)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from .podman_info import PodmanInfo
2+
from .podman_machine_inspect import PodmanMachineInspect, PodmanMachine
3+
4+
__all__ = [
5+
PodmanInfo,
6+
PodmanMachineInspect,
7+
PodmanMachine,
8+
]

0 commit comments

Comments
 (0)