Skip to content

podman_info

podman_info

Podman environment detection.

Grouped into three submodules so each concern stands on its own:

  • info — version + capability parsing.
  • hooks_dir — global hook directory discovery via containers.conf.
  • network — slirp4netns CIDR/gateway and resolv.conf parsing.

Public names are re-exported here for convenience; new code is welcome to import from the specific submodule when intent is clearer.

HOOK_JSON_FILENAME = 'terok-shield-createRuntime.json' module-attribute

HOOKS_DIR_PERSIST_VERSION = (99, 0, 0) module-attribute

__all__ = ['HOOKS_DIR_PERSIST_VERSION', 'HOOK_JSON_FILENAME', 'PodmanInfo', 'find_hooks_dirs', 'global_hooks_hint', 'has_global_hooks', 'parse_podman_info', 'parse_resolv_conf', 'parse_slirp4netns_cidr', 'slirp4netns_gateway'] module-attribute

PodmanInfo(version, rootless_network_cmd, pasta_executable, slirp4netns_executable) dataclass

Parsed podman environment information.

Constructed from podman info -f json output. Stateless — the caller manages caching.

version instance-attribute

rootless_network_cmd instance-attribute

pasta_executable instance-attribute

slirp4netns_executable instance-attribute

hooks_dir_persists property

Return True if --hooks-dir survives container restart.

Currently always False — podman drops per-container hooks-dir on stop/start even on 5.8.0 (issues #121, #122). The version gate will be lowered when podman fixes this upstream.

network_mode property

Determine the rootless network mode.

Uses rootlessNetworkCmd when available (podman 5+). When absent (podman 4.x), defaults to slirp4netns if its executable is available — podman 4.x defaults to slirp4netns.

find_hooks_dirs()

Find hooks directories podman would check.

Reads containers.conf (user config overrides system config). Returns the configured directories in precedence order (last wins for podman). Empty when no hooks_dir entry is configured — terok always patches containers.conf at setup time, so an empty result implies setup has not run.

Source code in src/terok_shield/podman_info/hooks_dir.py
def find_hooks_dirs() -> list[Path]:
    """Find hooks directories podman would check.

    Reads ``containers.conf`` (user config overrides system config).
    Returns the configured directories in precedence order (last wins
    for podman).  Empty when no ``hooks_dir`` entry is configured —
    terok always patches ``containers.conf`` at setup time, so an
    empty result implies setup has not run.
    """
    user_dirs = _parse_hooks_dir_from_conf(_user_containers_conf())
    if user_dirs:
        return [Path(d).expanduser() for d in user_dirs]

    for conf_path in reversed(_SYSTEM_CONF_PATHS):
        dirs = _parse_hooks_dir_from_conf(conf_path)
        if dirs:
            return [Path(d).expanduser() for d in dirs]

    return []

global_hooks_hint()

Short hint telling the user to run terok-shield setup.

Source code in src/terok_shield/podman_info/hooks_dir.py
def global_hooks_hint() -> str:
    """Short hint telling the user to run ``terok-shield setup``."""
    return (
        "Per-container --hooks-dir does not persist on container restart\n"
        "(ref: https://github.com/containers/podman/issues/17935).\n"
        "\n"
        "Run 'terok-shield setup' to install global hooks."
    )

has_global_hooks(hooks_dirs=None)

Check if terok-shield hooks are installed in any global hooks dir.

Parameters:

Name Type Description Default
hooks_dirs list[Path] | None

Directories to check (default: auto-detect via find_hooks_dirs).

None
Source code in src/terok_shield/podman_info/hooks_dir.py
def has_global_hooks(hooks_dirs: list[Path] | None = None) -> bool:
    """Check if terok-shield hooks are installed in any global hooks dir.

    Args:
        hooks_dirs: Directories to check (default: auto-detect via
            [`find_hooks_dirs`][terok_shield.podman_info.hooks_dir.find_hooks_dirs]).
    """
    if hooks_dirs is None:
        hooks_dirs = find_hooks_dirs()
    return any((d / HOOK_JSON_FILENAME).is_file() for d in hooks_dirs)

parse_podman_info(json_str)

Parse podman info -f json output into a PodmanInfo.

Returns a zero-version fallback on invalid or partially-malformed input — every nested section is coerced through an isinstance(..., dict) guard so a scalar/list where a table is expected can never produce an AttributeError.

Source code in src/terok_shield/podman_info/info.py
def parse_podman_info(json_str: str) -> PodmanInfo:
    """Parse ``podman info -f json`` output into a [`PodmanInfo`][terok_shield.podman_info.info.PodmanInfo].

    Returns a zero-version fallback on invalid or partially-malformed input —
    every nested section is coerced through an ``isinstance(..., dict)``
    guard so a scalar/list where a table is expected can never produce
    an ``AttributeError``.
    """
    try:
        info = json.loads(json_str)
    except (json.JSONDecodeError, TypeError):
        info = None

    if not isinstance(info, dict):
        return PodmanInfo(
            version=(0,),
            rootless_network_cmd="",
            pasta_executable="",
            slirp4netns_executable="",
        )

    host = _as_dict(info.get("host"))
    version_section = _as_dict(info.get("version"))
    pasta = _as_dict(host.get("pasta"))
    slirp = _as_dict(host.get("slirp4netns"))

    return PodmanInfo(
        version=_parse_version(_as_str(version_section.get("Version"), "0")),
        rootless_network_cmd=_as_str(host.get("rootlessNetworkCmd")),
        pasta_executable=_as_str(pasta.get("executable")),
        slirp4netns_executable=_as_str(slirp.get("executable")),
    )

parse_resolv_conf(text)

Extract the first nameserver address from resolv.conf content.

Returns an empty string if no valid nameserver line is found.

Source code in src/terok_shield/podman_info/network.py
def parse_resolv_conf(text: str) -> str:
    """Extract the first ``nameserver`` address from resolv.conf content.

    Returns an empty string if no valid nameserver line is found.
    """
    for line in text.splitlines():
        parts = line.strip().split()
        if len(parts) >= 2 and parts[0] == "nameserver":
            return parts[1]
    return ""

parse_slirp4netns_cidr()

Read the slirp4netns CIDR from containers.conf, or return the default.

User config (XDG) is checked first in rootless mode, then system paths. When running as root, user config is skipped to prevent untrusted XDG_CONFIG_HOME from influencing firewall rules.

Source code in src/terok_shield/podman_info/network.py
def parse_slirp4netns_cidr() -> str:
    """Read the slirp4netns CIDR from ``containers.conf``, or return the default.

    User config (XDG) is checked first in rootless mode, then system paths.
    When running as root, user config is skipped to prevent untrusted
    ``XDG_CONFIG_HOME`` from influencing firewall rules.
    """
    paths = list(reversed(_SYSTEM_CONF_PATHS))
    if os.geteuid() != 0:
        paths.insert(0, _user_containers_conf())
    for path in paths:
        for opt in _parse_network_cmd_options(path):
            if opt.startswith("cidr="):
                return opt.split("=", 1)[1]
    return _DEFAULT_SLIRP4NETNS_CIDR

slirp4netns_gateway(cidr=None)

Compute the slirp4netns gateway address (CIDR base + 2).

Reads containers.conf for a cidr= override when cidr is None. Falls back to the default CIDR on malformed input.

Source code in src/terok_shield/podman_info/network.py
def slirp4netns_gateway(cidr: str | None = None) -> str:
    """Compute the slirp4netns gateway address (``CIDR base + 2``).

    Reads ``containers.conf`` for a ``cidr=`` override when *cidr* is None.
    Falls back to the default CIDR on malformed input.
    """
    try:
        net = ipaddress.IPv4Network(cidr or parse_slirp4netns_cidr())
    except ValueError:
        net = ipaddress.IPv4Network(_DEFAULT_SLIRP4NETNS_CIDR)
    return str(net.network_address + 2)