From 89dcb89d5938d2866b6301d6032b8e4631dc484c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Dan=C4=9Bk?= Date: Tue, 4 Feb 2025 17:20:40 +0100 Subject: [PATCH] NO-JIRA: chore(tests/containers): detect socket for ryuk's use with rootless podman on macOS --- README.md | 2 +- poetry.lock | 21 +- pyproject.toml | 1 + tests/containers/conftest.py | 12 +- tests/containers/docker_utils.py | 19 + tests/containers/podman_machine_utils.py | 6 +- tests/containers/pydantic_schemas/__init__.py | 8 + .../pydantic_schemas/podman_info.py | 468 ++++++++++++++++++ .../podman_machine_inspect.py | 89 ++++ tests/containers/schemas.py | 41 -- 10 files changed, 620 insertions(+), 47 deletions(-) create mode 100644 tests/containers/pydantic_schemas/__init__.py create mode 100644 tests/containers/pydantic_schemas/podman_info.py create mode 100644 tests/containers/pydantic_schemas/podman_machine_inspect.py delete mode 100644 tests/containers/schemas.py diff --git a/README.md b/README.md index 961e59078..f11c3e602 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock poetry run pytest tests/con # Mac OS brew install podman podman machine init -podman machine set --rootful +podman machine set --rootful=false sudo podman-mac-helper install podman machine start poetry run pytest tests/containers --image quay.io/opendatahub/workbench-images@sha256:e98d19df346e7abb1fa3053f6d41f0d1fa9bab39e49b4cb90b510ca33452c2e4 diff --git a/poetry.lock b/poetry.lock index ab1515a40..a7f0d60c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -269,6 +269,25 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "podman" +version = "5.2.0" +description = "Bindings for Podman RESTful API" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "podman-5.2.0-py3-none-any.whl", hash = "sha256:e42e907b44af3e8578ac3bd4838040f7dd6b4af091f787e51462b979746703eb"}, + {file = "podman-5.2.0.tar.gz", hash = "sha256:6a064a3e9dbfc4ee0ee0c51604164e1f58d9d00b10f702c3e570651aee722ec7"}, +] + +[package.dependencies] +requests = ">=2.24" +urllib3 = "*" + +[package.extras] +progress-bar = ["rich (>=12.5.1)"] + [[package]] name = "pydantic" version = "2.10.6" @@ -694,4 +713,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "~3.12" -content-hash = "f525d20baef6a062a045c13772f17d8e30b7fa14609069a13872ce570191240f" +content-hash = "29d9a9933892cb8c8d8d8c969a70acb157d49b748c93723afe20c25eab69297d" diff --git a/pyproject.toml b/pyproject.toml index 2e494b0bc..a1c32cedb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ pytest-subtests = "^0.14.1" pyfakefs = "^5.7.4" testcontainers = "^4.9.1" docker = "^7.1.0" +podman = "^5.2.0" pydantic = "^2.10.6" requests = "^2.32.3" diff --git a/tests/containers/conftest.py b/tests/containers/conftest.py index a3c49f7ef..ff09fc598 100644 --- a/tests/containers/conftest.py +++ b/tests/containers/conftest.py @@ -2,6 +2,7 @@ import logging import os +import platform from typing import Iterable, Callable, TYPE_CHECKING import testcontainers.core.config @@ -64,7 +65,16 @@ def pytest_sessionstart(session: Session) -> None: # set that socket path for ryuk's use, unless user overrode that if TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE not in os.environ: - testcontainers.core.config.testcontainers_config.ryuk_docker_socket = socket_path + logging.info(f"Env variable TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE not set, setting it now") + if platform.system().lower() == 'linux': + logging.info(f"We are on Linux, setting {socket_path=} for TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") + testcontainers.core.config.testcontainers_config.ryuk_docker_socket = socket_path + elif platform.system().lower() == 'darwin': + podman_machine_socket_path = docker_utils.get_podman_machine_socket_path(client.client) + logging.info(f"We are on macOS, setting {podman_machine_socket_path=} for TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") + testcontainers.core.config.testcontainers_config.ryuk_docker_socket = podman_machine_socket_path + else: + raise RuntimeError(f"Unsupported platform {platform.system()=}, cannot set TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE") # second preflight check: start the Reaper container if not testcontainers.core.config.testcontainers_config.ryuk_disabled: diff --git a/tests/containers/docker_utils.py b/tests/containers/docker_utils.py index 6dddab148..6474e480f 100644 --- a/tests/containers/docker_utils.py +++ b/tests/containers/docker_utils.py @@ -8,10 +8,13 @@ import time from typing import Iterable, TYPE_CHECKING +import podman import docker.client import testcontainers.core.container +import tests.containers.pydantic_schemas + if TYPE_CHECKING: from docker.models.containers import Container @@ -158,6 +161,7 @@ def communicate(self, line_prefix=b"") -> int: raise RuntimeError("Hm could that really happen?") return self.poll() + def get_socket_path(client: docker.client.DockerClient) -> str: """Determine the local socket path. This works even when `podman machine` with its own host-mounts is involved @@ -165,6 +169,21 @@ def get_socket_path(client: docker.client.DockerClient) -> str: socket_path = _the_one(adapter.socket_path for adapter in client.api.adapters.values()) return socket_path + +def get_podman_machine_socket_path(docker_client: docker.client.DockerClient) -> str: + """Determine the podman socket path that's valid from inside Podman Machine. + * rootful podman: both the host (`ls`) and podman machine (`podman machine ssh ls`) have it at `/var/run/docker.sock`. + * rootless podman: the location on host is still the same while podman machine has it in `/var/run/user/${PID}/podman/podman.sock`. + """ + socket_path = get_socket_path(docker_client) + podman_client = podman.PodmanClient(base_url="http+unix://" + socket_path) + info = tests.containers.pydantic_schemas.PodmanInfo.model_validate(podman_client.info()) + assert info.host.remoteSocket.exists, "Failed to determine the podman remote socket" + assert info.host.remoteSocket.path.startswith("unix://"), "Unexpected remote socket path" + machine_socket_path = info.host.remoteSocket.path[len("unix://"):] + return machine_socket_path + + def get_container_pid(container: Container) -> int | None: """Get the network namespace of a Docker container.""" container.reload() diff --git a/tests/containers/podman_machine_utils.py b/tests/containers/podman_machine_utils.py index c07a152f7..496078a72 100644 --- a/tests/containers/podman_machine_utils.py +++ b/tests/containers/podman_machine_utils.py @@ -4,16 +4,16 @@ import subprocess from typing import Callable -import tests.containers.schemas +import tests.containers.pydantic_schemas logging.basicConfig(level=logging.DEBUG) -def open_ssh_tunnel(machine_predicate: Callable[[tests.containers.schemas.PodmanMachine], bool], +def open_ssh_tunnel(machine_predicate: Callable[[tests.containers.pydantic_schemas.PodmanMachine], bool], local_port: int, remote_port: int, remote_interface: str = "localhost") -> subprocess.Popen: # Load and parse the Podman machine data machine_names = subprocess.check_output(["podman", "machine", "list", "--quiet"], text=True).splitlines() json_data = subprocess.check_output(["podman", "machine", "inspect", *machine_names], text=True) - inspect = tests.containers.schemas.PodmanMachineInspect(machines=json.loads(json_data)) + inspect = tests.containers.pydantic_schemas.PodmanMachineInspect(machines=json.loads(json_data)) machines = inspect.machines machine = next((m for m in machines if machine_predicate(m)), None) diff --git a/tests/containers/pydantic_schemas/__init__.py b/tests/containers/pydantic_schemas/__init__.py new file mode 100644 index 000000000..efbec45e9 --- /dev/null +++ b/tests/containers/pydantic_schemas/__init__.py @@ -0,0 +1,8 @@ +from .podman_info import PodmanInfo +from .podman_machine_inspect import PodmanMachineInspect, PodmanMachine + +__all__ = [ + PodmanInfo, + PodmanMachineInspect, + PodmanMachine, +] diff --git a/tests/containers/pydantic_schemas/podman_info.py b/tests/containers/pydantic_schemas/podman_info.py new file mode 100644 index 000000000..89b2abe6a --- /dev/null +++ b/tests/containers/pydantic_schemas/podman_info.py @@ -0,0 +1,468 @@ +from __future__ import annotations + +from pydantic import BaseModel, Field +from typing import List, Optional, Dict + + +class Conmon(BaseModel): + package: str + path: str + version: str + + +class CpuUtilization(BaseModel): + idlePercent: float + systemPercent: float + userPercent: float + + +class Distribution(BaseModel): + distribution: str + variant: str + version: str + + +class IdMapping(BaseModel): + container_id: int + host_id: int + size: int + + +class IdMappings(BaseModel): + gidmap: List[IdMapping] | None = None + uidmap: List[IdMapping] | None = None + + +class NetworkBackendInfo(BaseModel): + backend: str + dns: Dict[str, str] + package: str + path: str + version: str + + +class OciRuntime(BaseModel): + name: str + package: str + path: str + version: str + + +class Pasta(BaseModel): + executable: str + package: str + version: str + + +class RemoteSocket(BaseModel): + exists: bool + path: str + + +class Security(BaseModel): + apparmorEnabled: bool + capabilities: str + rootless: bool + seccompEnabled: bool + seccompProfilePath: str + selinuxEnabled: bool + + +class Slirp4netns(BaseModel): + executable: str + package: str + version: str + + +class Host(BaseModel): + arch: str + buildahVersion: str + cgroupControllers: List[str] + cgroupManager: str + cgroupVersion: str + conmon: Conmon + cpuUtilization: CpuUtilization + cpus: int + databaseBackend: str + distribution: Distribution + eventLogger: str + freeLocks: int + hostname: str + idMappings: IdMappings + kernel: str + linkmode: str + logDriver: str + memFree: int + memTotal: int + networkBackend: str + networkBackendInfo: NetworkBackendInfo + ociRuntime: OciRuntime + os: str + pasta: Pasta + remoteSocket: RemoteSocket + rootlessNetworkCmd: str + security: Security + serviceIsRemote: bool + slirp4netns: Slirp4netns + swapFree: int + swapTotal: int + uptime: str + variant: str + + +class Plugins(BaseModel): + authorization: Optional[str] = None + log: List[str] + network: List[str] + volume: List[str] + + +class Registries(BaseModel): + search: List[str] + + +class ContainerStore(BaseModel): + number: int + paused: int + running: int + stopped: int + + +class GraphStatus(BaseModel): + backing_filesystem: str = Field(..., alias="Backing Filesystem") + native_overlay_diff: str = Field(..., alias="Native Overlay Diff") + supports_d_type: str = Field(..., alias="Supports d_type") + supports_shifting: str = Field(..., alias="Supports shifting") + supports_volatile: str = Field(..., alias="Supports volatile") + using_metacopy: str = Field(..., alias="Using metacopy") + + +class ImageStore(BaseModel): + number: int + + +class Store(BaseModel): + configFile: str + containerStore: ContainerStore + graphDriverName: str + graphOptions: Dict[str, str] + graphRoot: str + graphRootAllocated: int + graphRootUsed: int + graphStatus: GraphStatus + imageCopyTmpDir: str + imageStore: ImageStore + runRoot: str + transientStore: bool + volumePath: str + + +class Version(BaseModel): + APIVersion: str + Built: int + BuiltTime: str + GitCommit: str + GoVersion: str + Os: str + OsArch: str + Version: str + + +class PodmanInfo(BaseModel): + host: Host + plugins: Plugins + registries: Registries + store: Store + version: Version + + +def test_podman_info(): + # given + rootful_podman_info = PodmanInfo.model_validate({ + 'host': { + 'arch': 'arm64', + 'buildahVersion': '1.38.1', + 'cgroupControllers': [ + 'cpuset', + 'cpu', + 'io', + 'memory', + 'pids', + 'rdma', + 'misc'], + 'cgroupManager': 'systemd', + 'cgroupVersion': 'v2', + 'conmon': { + 'package': 'conmon-2.1.12-3.fc41.aarch64', + 'path': '/usr/bin/conmon', + 'version': 'conmon version 2.1.12, commit: '}, + 'cpuUtilization': { + 'idlePercent': 97.99, + 'systemPercent': 1.15, + 'userPercent': 0.86}, + 'cpus': 6, + 'databaseBackend': 'sqlite', + 'distribution': { + 'distribution': 'fedora', + 'variant': 'coreos', + 'version': '41'}, + 'eventLogger': 'journald', + 'freeLocks': 2048, + 'hostname': 'localhost.localdomain', + 'idMappings': {'gidmap': None, 'uidmap': None}, + 'kernel': '6.12.7-200.fc41.aarch64', + 'linkmode': 'dynamic', + 'logDriver': 'journald', + 'memFree': 1624608768, + 'memTotal': 2041810944, + 'networkBackend': 'netavark', + 'networkBackendInfo': { + 'backend': 'netavark', + 'dns': { + 'package': 'aardvark-dns-1.13.1-1.fc41.aarch64', + 'path': '/usr/libexec/podman/aardvark-dns', + 'version': 'aardvark-dns 1.13.1'}, + 'package': 'netavark-1.13.1-1.fc41.aarch64', + 'path': '/usr/libexec/podman/netavark', + 'version': 'netavark 1.13.1'}, + 'ociRuntime': { + 'name': 'crun', + 'package': 'crun-1.19.1-1.fc41.aarch64', + 'path': '/usr/bin/crun', + 'version': 'crun version 1.19.1\n' + 'commit: ' + '3e32a70c93f5aa5fea69b50256cca7fd4aa23c80\n' + 'rundir: /run/crun\n' + 'spec: 1.0.0\n' + '+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP ' + '+EBPF +CRIU +LIBKRUN +WASM:wasmedge ' + '+YAJL'}, + 'os': 'linux', + 'pasta': {'executable': '/usr/bin/pasta', + 'package': 'passt-0^20241211.g09478d5-1.fc41.aarch64', + 'version': 'pasta ' + '0^20241211.g09478d5-1.fc41.aarch64-pasta\n' + 'Copyright Red Hat\n' + 'GNU General Public License, version 2 or ' + 'later\n' + ' ' + '\n' + 'This is free software: you are free to change ' + 'and redistribute it.\n' + 'There is NO WARRANTY, to the extent permitted ' + 'by law.\n'}, + 'remoteSocket': { + 'exists': True, + 'path': 'unix:///run/podman/podman.sock'}, + 'rootlessNetworkCmd': 'pasta', + 'security': { + 'apparmorEnabled': False, + 'capabilities': 'CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT', + 'rootless': False, + 'seccompEnabled': True, + 'seccompProfilePath': '/usr/share/containers/seccomp.json', + 'selinuxEnabled': True}, + 'serviceIsRemote': False, + 'slirp4netns': { + 'executable': '/usr/bin/slirp4netns', + 'package': 'slirp4netns-1.3.1-1.fc41.aarch64', + 'version': 'slirp4netns version 1.3.1\n' + 'commit: ' + 'e5e368c4f5db6ae75c2fce786e31eef9da6bf236\n' + 'libslirp: 4.8.0\n' + 'SLIRP_CONFIG_VERSION_MAX: 5\n' + 'libseccomp: 2.5.5'}, + 'swapFree': 0, + 'swapTotal': 0, + 'uptime': '0h 1m 33.00s', + 'variant': 'v8'}, + 'plugins': { + 'authorization': None, + 'log': ['k8s-file', 'none', 'passthrough', 'journald'], + 'network': ['bridge', 'macvlan', 'ipvlan'], + 'volume': ['local']}, + 'registries': { + 'search': ['docker.io']}, + 'store': { + 'configFile': '/usr/share/containers/storage.conf', + 'containerStore': { + 'number': 0, + 'paused': 0, + 'running': 0, + 'stopped': 0}, + 'graphDriverName': 'overlay', + 'graphOptions': { + 'overlay.imagestore': '/usr/lib/containers/storage', + 'overlay.mountopt': 'nodev,metacopy=on'}, + 'graphRoot': '/var/lib/containers/storage', + 'graphRootAllocated': 106415992832, + 'graphRootUsed': 15990263808, + 'graphStatus': { + 'Backing Filesystem': 'xfs', + 'Native Overlay Diff': 'false', + 'Supports d_type': 'true', + 'Supports shifting': 'true', + 'Supports volatile': 'true', + 'Using metacopy': 'true'}, + 'imageCopyTmpDir': '/var/tmp', + 'imageStore': {'number': 15}, + 'runRoot': '/run/containers/storage', + 'transientStore': False, + 'volumePath': '/var/lib/containers/storage/volumes'}, + 'version': { + 'APIVersion': '5.3.2', + 'Built': 1737504000, + 'BuiltTime': 'Wed Jan 22 01:00:00 2025', + 'GitCommit': '', + 'GoVersion': 'go1.23.4', + 'Os': 'linux', + 'OsArch': 'linux/arm64', + 'Version': '5.3.2'}}) + + rootless_podman_info = PodmanInfo.model_validate({ + 'host': { + 'arch': 'arm64', + 'buildahVersion': '1.38.1', + 'cgroupControllers': ['cpu', 'io', 'memory', 'pids'], + 'cgroupManager': 'systemd', + 'cgroupVersion': 'v2', + 'conmon': { + 'package': 'conmon-2.1.12-3.fc41.aarch64', + 'path': '/usr/bin/conmon', + 'version': 'conmon version 2.1.12, commit: '}, + 'cpuUtilization': { + 'idlePercent': 99.34, + 'systemPercent': 0.35, + 'userPercent': 0.31}, + 'cpus': 6, + 'databaseBackend': 'sqlite', + 'distribution': { + 'distribution': 'fedora', + 'variant': 'coreos', + 'version': '41'}, + 'eventLogger': 'journald', + 'freeLocks': 2047, + 'hostname': 'localhost.localdomain', + 'idMappings': { + 'gidmap': [{'container_id': 0, + 'host_id': 1000, + 'size': 1}, + {'container_id': 1, + 'host_id': 100000, + 'size': 1000000}], + 'uidmap': [{'container_id': 0, + 'host_id': 501, + 'size': 1}, + {'container_id': 1, + 'host_id': 100000, + 'size': 1000000}]}, + 'kernel': '6.12.7-200.fc41.aarch64', + 'linkmode': 'dynamic', + 'logDriver': 'journald', + 'memFree': 1607774208, + 'memTotal': 2041810944, + 'networkBackend': 'netavark', + 'networkBackendInfo': { + 'backend': 'netavark', + 'dns': { + 'package': 'aardvark-dns-1.13.1-1.fc41.aarch64', + 'path': '/usr/libexec/podman/aardvark-dns', + 'version': 'aardvark-dns 1.13.1'}, + 'package': 'netavark-1.13.1-1.fc41.aarch64', + 'path': '/usr/libexec/podman/netavark', + 'version': 'netavark 1.13.1'}, + 'ociRuntime': { + 'name': 'crun', + 'package': 'crun-1.19.1-1.fc41.aarch64', + 'path': '/usr/bin/crun', + 'version': 'crun version 1.19.1\n' + 'commit: ' + '3e32a70c93f5aa5fea69b50256cca7fd4aa23c80\n' + 'rundir: /run/user/501/crun\n' + 'spec: 1.0.0\n' + '+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP ' + '+EBPF +CRIU +LIBKRUN +WASM:wasmedge ' + '+YAJL'}, + 'os': 'linux', + 'pasta': { + 'executable': '/usr/bin/pasta', + 'package': 'passt-0^20241211.g09478d5-1.fc41.aarch64', + 'version': 'pasta ' + '0^20241211.g09478d5-1.fc41.aarch64-pasta\n' + 'Copyright Red Hat\n' + 'GNU General Public License, version 2 or ' + 'later\n' + ' ' + '\n' + 'This is free software: you are free to change ' + 'and redistribute it.\n' + 'There is NO WARRANTY, to the extent permitted ' + 'by law.\n'}, + 'remoteSocket': { + 'exists': True, + 'path': 'unix:///run/user/501/podman/podman.sock'}, + 'rootlessNetworkCmd': 'pasta', + 'security': { + 'apparmorEnabled': False, + 'capabilities': 'CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT', + 'rootless': True, + 'seccompEnabled': True, + 'seccompProfilePath': '/usr/share/containers/seccomp.json', + 'selinuxEnabled': True}, + 'serviceIsRemote': False, + 'slirp4netns': { + 'executable': '/usr/bin/slirp4netns', + 'package': 'slirp4netns-1.3.1-1.fc41.aarch64', + 'version': 'slirp4netns version 1.3.1\n' + 'commit: ' + 'e5e368c4f5db6ae75c2fce786e31eef9da6bf236\n' + 'libslirp: 4.8.0\n' + 'SLIRP_CONFIG_VERSION_MAX: 5\n' + 'libseccomp: 2.5.5'}, + 'swapFree': 0, + 'swapTotal': 0, + 'uptime': '0h 2m 2.00s', + 'variant': 'v8'}, + 'plugins': { + 'authorization': None, + 'log': ['k8s-file', 'none', 'passthrough', 'journald'], + 'network': ['bridge', 'macvlan', 'ipvlan'], + 'volume': ['local']}, + 'registries': { + 'search': ['docker.io']}, + 'store': { + 'configFile': '/var/home/core/.config/containers/storage.conf', + 'containerStore': { + 'number': 1, + 'paused': 0, + 'running': 0, + 'stopped': 1}, + 'graphDriverName': 'overlay', + 'graphOptions': {}, + 'graphRoot': '/var/home/core/.local/share/containers/storage', + 'graphRootAllocated': 106415992832, + 'graphRootUsed': 15990403072, + 'graphStatus': { + 'Backing Filesystem': 'xfs', + 'Native Overlay Diff': 'true', + 'Supports d_type': 'true', + 'Supports shifting': 'false', + 'Supports volatile': 'true', + 'Using metacopy': 'false'}, + 'imageCopyTmpDir': '/var/tmp', + 'imageStore': {'number': 2}, + 'runRoot': '/run/user/501/containers', + 'transientStore': False, + 'volumePath': '/var/home/core/.local/share/containers/storage/volumes'}, + 'version': { + 'APIVersion': '5.3.2', + 'Built': 1737504000, + 'BuiltTime': 'Wed Jan 22 01:00:00 2025', + 'GitCommit': '', + 'GoVersion': 'go1.23.4', + 'Os': 'linux', + 'OsArch': 'linux/arm64', + 'Version': '5.3.2'}}) + + assert rootful_podman_info.host.remoteSocket.exists + assert rootless_podman_info.host.remoteSocket.exists diff --git a/tests/containers/pydantic_schemas/podman_machine_inspect.py b/tests/containers/pydantic_schemas/podman_machine_inspect.py new file mode 100644 index 000000000..9df801520 --- /dev/null +++ b/tests/containers/pydantic_schemas/podman_machine_inspect.py @@ -0,0 +1,89 @@ +import json + +from pydantic import BaseModel +from typing import Optional +from datetime import datetime + + +class ConfigDir(BaseModel): + Path: str + + +class PodmanSocket(BaseModel): + Path: str + + +class ConnectionInfo(BaseModel): + PodmanSocket: PodmanSocket + PodmanPipe: Optional[str] = None + + +class Resources(BaseModel): + CPUs: int + DiskSize: int + Memory: int + USBs: list[str] = [] + + +class SSHConfig(BaseModel): + IdentityPath: str + Port: int + RemoteUsername: str + + +class PodmanMachine(BaseModel): + ConfigDir: ConfigDir + ConnectionInfo: ConnectionInfo + Created: datetime + LastUp: datetime + Name: str + Resources: Resources + SSHConfig: SSHConfig + State: str + UserModeNetworking: bool + Rootful: bool + Rosetta: bool + + +# generated from `podman machine inspect` output by smart tooling +class PodmanMachineInspect(BaseModel): + machines: list[PodmanMachine] + + +def test_podman_machine_inspect(): + # given + podman_machine_inspect = PodmanMachineInspect(machines=json.loads("""\ +[ + { + "ConfigDir": { + "Path": "/Users/jdanek/.config/containers/podman/machine/applehv" + }, + "ConnectionInfo": { + "PodmanSocket": { + "Path": "/var/folders/f1/3m518k5d34l72v_9nqyjzqm80000gn/T/podman/podman-machine-default-api.sock" + }, + "PodmanPipe": null + }, + "Created": "2025-01-28T12:36:07.415697+01:00", + "LastUp": "2025-01-29T09:37:49.361334+01:00", + "Name": "podman-machine-default", + "Resources": { + "CPUs": 6, + "DiskSize": 100, + "Memory": 2048, + "USBs": [] + }, + "SSHConfig": { + "IdentityPath": "/Users/jdanek/.local/share/containers/podman/machine/machine", + "Port": 53903, + "RemoteUsername": "core" + }, + "State": "running", + "UserModeNetworking": true, + "Rootful": true, + "Rosetta": true + } +] +""")) + + assert podman_machine_inspect.machines[0].Name == "podman-machine-default" diff --git a/tests/containers/schemas.py b/tests/containers/schemas.py deleted file mode 100644 index 1d87f6cad..000000000 --- a/tests/containers/schemas.py +++ /dev/null @@ -1,41 +0,0 @@ -from pydantic import BaseModel -from typing import Optional -from datetime import datetime - -class ConfigDir(BaseModel): - Path: str - -class PodmanSocket(BaseModel): - Path: str - -class ConnectionInfo(BaseModel): - PodmanSocket: PodmanSocket - PodmanPipe: Optional[str] = None - -class Resources(BaseModel): - CPUs: int - DiskSize: int - Memory: int - USBs: list[str] = [] - -class SSHConfig(BaseModel): - IdentityPath: str - Port: int - RemoteUsername: str - -class PodmanMachine(BaseModel): - ConfigDir: ConfigDir - ConnectionInfo: ConnectionInfo - Created: datetime - LastUp: datetime - Name: str - Resources: Resources - SSHConfig: SSHConfig - State: str - UserModeNetworking: bool - Rootful: bool - Rosetta: bool - -# generated from `podman machine inspect` output by smart tooling -class PodmanMachineInspect(BaseModel): - machines: list[PodmanMachine]