podman
podman
¶
Podman backend for the .protocol container runtime.
This is the concrete default runtime. Every subprocess call that
ends in a podman invocation lives in this module — other layers
speak only through the protocol.
The public export is PodmanRuntime plus the argv helpers that
terok_sandbox.sandbox.Sandbox uses to assemble podman run
commands (which remain podman-specific for now; a krun backend will
replace Sandbox.run when Phase 3 lands).
PodmanContainer(name, *, runtime)
¶
Podman implementation of Container.
Cheap to construct — does not verify existence. Each property /
method does a fresh podman inspect or equivalent.
Source code in src/terok_sandbox/runtime/podman.py
name = name
instance-attribute
¶
state
property
¶
Lifecycle state ("running", "exited", ...) or None.
running
property
¶
Shortcut: state == "running".
started_at
property
¶
Unix timestamp of the container's last start, or None.
{{.State.StartedAt.Unix}} sidesteps parsing podman's
RFC 3339 output (whose sub-second precision varies by version).
image
property
¶
Handle to the image this container was created from, or None.
rw_size
property
¶
Writable-layer size in bytes, or None if unavailable.
Uses podman container inspect --size — expect a brief pause
for large containers while overlay diffs are computed.
id
property
¶
Full container ID (podman .Id), or None when the container is absent.
mounts
property
¶
Bind/volume mounts as (host_source, container_destination) pairs.
Reads podman inspect .Mounts as JSON and projects each
entry to its host source and in-container destination — the two
fields an operator needs to answer "is my code mounted where I
think it is?". Empty list on an absent container, no mounts, or
any inspect/parse failure (matching the absent-is-empty contract
the other handle properties follow).
__repr__()
¶
__eq__(other)
¶
__hash__()
¶
start()
¶
Start the container.
Every lifecycle failure — missing podman, timeout, or non-zero
exit — surfaces as RuntimeError so callers have a
single exception type to catch. The original exception is
preserved via __cause__ when applicable.
Source code in src/terok_sandbox/runtime/podman.py
stop(*, timeout=10)
¶
Stop the container: SIGTERM, then SIGKILL after timeout seconds.
The podman stop client gets no single wall-clock budget —
how long a stop may take is decided by watching the container,
not by guessing. The grace period belongs to the container;
once it expires the SIGKILL has been sent, so the container must
leave running within _STOP_KILL_TIMEOUT. From the
moment it is no longer running, what remains is podman-side
teardown (unmounts, network, poststop hooks), which gets its own
_STOP_CLEANUP_TIMEOUT — teardown time is unrelated to the
grace period, so one must never eat into the other.
Every lifecycle failure surfaces as RuntimeError; see
start for the rationale.
Source code in src/terok_sandbox/runtime/podman.py
wait(timeout=None)
¶
Block until the container exits; return its exit code.
Raises TimeoutError on timeout, RuntimeError on
podman wait failures or non-numeric output.
Source code in src/terok_sandbox/runtime/podman.py
copy_in(src, dest)
¶
Copy a host path into the (stopped) container at dest.
Directories are copied contents-first (src/.) so existing
container contents at dest are preserved and augmented.
Source code in src/terok_sandbox/runtime/podman.py
login_command(*, command=_DEFAULT_LOGIN_COMMAND)
¶
Return an argv for os.execvp to attach interactively.
Empty command uses the default tmux session.
Source code in src/terok_sandbox/runtime/podman.py
logs(*, follow=False, tail=None)
¶
Return a context-managed iterator over decoded log lines.
stream_initial_logs(ready_check, timeout_sec)
¶
Stream logs until ready_check matches or timeout_sec elapses.
Prints each line to stdout as it arrives. Returns True when
the ready marker is observed, False on timeout.
Source code in src/terok_sandbox/runtime/podman.py
PodmanImage(ref, *, repository='', tag='', size='', created='')
¶
Podman implementation of Image.
Values listed by podman images (repository, tag, size,
created) may be pre-populated at construction to avoid an extra
inspect; when absent they fall back to empty strings.
Source code in src/terok_sandbox/runtime/podman.py
ref = ref
instance-attribute
¶
id
property
¶
Resolved image ID, or None when the image is absent.
repository
property
¶
Repository portion (pre-populated or "").
tag
property
¶
Tag portion (pre-populated or "").
size
property
¶
Podman-rendered size string (pre-populated or "").
created
property
¶
Podman-rendered creation timestamp (pre-populated or "").
__repr__()
¶
__eq__(other)
¶
__hash__()
¶
exists()
¶
Return True if the image is present locally.
Source code in src/terok_sandbox/runtime/podman.py
labels()
¶
Return the OCI Config.Labels as a flat string dict.
Source code in src/terok_sandbox/runtime/podman.py
history()
¶
Return the CreatedBy string of each layer, top to bottom.
Source code in src/terok_sandbox/runtime/podman.py
remove()
¶
Remove the image; return True on success.
No force flag — an image referenced by a running container stays, which matches cleanup semantics (sweeping safe garbage, not reaping live state).
Source code in src/terok_sandbox/runtime/podman.py
PodmanLogStream(container_name, *, follow, tail)
¶
Iterator over podman log lines.
Wraps podman logs [-f] [--tail N] in a subprocess.Popen and
yields decoded lines. __exit__ terminates the child; calling
close() mid-iteration has the same effect.
Source code in src/terok_sandbox/runtime/podman.py
process
property
¶
Underlying Popen handle — exposed for callers needing low-level access.
__iter__()
¶
__next__()
¶
Read the next decoded log line; raise StopIteration at EOF.
Source code in src/terok_sandbox/runtime/podman.py
__enter__()
¶
__exit__(*exc)
¶
close()
¶
Terminate the underlying podman logs process and release its pipes.
Reaps the child (terminate → wait → kill fallback) and then
closes both parent-side file descriptors so repeated
container.logs() calls do not leak FDs. Safe to call
multiple times; second call is a no-op.
Source code in src/terok_sandbox/runtime/podman.py
ContainerEvent(name, status)
dataclass
¶
PodmanEventStream(prefix)
¶
Iterator over podman container lifecycle events for a name prefix.
The push-based companion to
container_states:
wraps podman events --filter type=container --format '{{json .}}' in a
subprocess.Popen and yields
ContainerEvent records whose
container name starts with <prefix>-. Iteration blocks between events;
close() (or __exit__) terminates the child, which unblocks a consumer
thread parked in __next__.
Source code in src/terok_sandbox/runtime/podman.py
process
property
¶
Underlying Popen handle — exposed for callers needing the fd.
__iter__()
¶
__next__()
¶
Block until the next matching lifecycle event; stop at EOF.
Source code in src/terok_sandbox/runtime/podman.py
__enter__()
¶
__exit__(*exc)
¶
close()
¶
Terminate the podman events child and release its pipe.
Reaps the child (terminate → wait → kill fallback) and closes the parent-side fd so a long-running TUI doesn't leak one per project switch. Safe to call multiple times; the second call is a no-op.
Source code in src/terok_sandbox/runtime/podman.py
PodmanPortReservation(host='127.0.0.1')
¶
Holds a TCP port open until released.
Bind on construction; the reserved port number is exposed via the
port attribute. Caller is responsible for closing (directly via
close or via with).
Source code in src/terok_sandbox/runtime/podman.py
PodmanRuntime
¶
The default ContainerRuntime — talks to the podman CLI.
container(name)
¶
containers_with_prefix(prefix)
¶
Return handles for every container whose name starts with prefix-.
Single podman ps -a call under the hood; the returned handles
are lazy (fresh inspect on property access).
Source code in src/terok_sandbox/runtime/podman.py
image(ref)
¶
images(*, dangling_only=False)
¶
Enumerate local images.
dangling_only narrows to untagged <none>:<none> entries.
Source code in src/terok_sandbox/runtime/podman.py
exec(container, cmd, *, timeout=None)
¶
Run cmd inside container via podman exec.
Lets FileNotFoundError (podman missing) and
subprocess.TimeoutExpired propagate unchanged.
Raises ValueError if cmd is empty — podman exec with
no argv is never a valid request and catching it here avoids a
later IndexError in the debug log.
Source code in src/terok_sandbox/runtime/podman.py
exec_stdio(container, cmd, *, stdin, stdout, stderr=None, env=None, timeout=None)
¶
Bridge byte streams to podman exec -i for cmd inside container.
Synchronous: spawns the child, runs three daemon pump threads
(one per direction) copying bytes until either side reaches
EOF or the child exits, joins the pumps, returns the exit code.
Async callers drive this via
run_in_executor.
Lets FileNotFoundError (podman missing) propagate. On
timeout, terminates the child (terminate → 2 s wait → kill) and
re-raises TimeoutExpired.
Source code in src/terok_sandbox/runtime/podman.py
force_remove(containers)
¶
Best-effort podman rm -f of each container.
Continues through individual failures. An already-absent container counts as removed — the post-condition holds.
Source code in src/terok_sandbox/runtime/podman.py
reserve_port(host='127.0.0.1')
¶
container_states(prefix)
¶
Return {container_name: state} for matching containers.
Optimisation over [c.state for c in containers_with_prefix(prefix)]
— single podman ps -a instead of N inspects. Backend-specific;
not part of the ContainerRuntime protocol.
Reads --format json and the raw State field rather than the
{{.State}} template: the template is presentation-layer and
version-unstable (podman 3.4 renders a running container as
Up 2 minutes ago, so every task displayed as stopped there),
while the JSON field carries the bare state on every podman version.
Returns:
| Type | Description |
|---|---|
dict[str, str] | None
|
|
dict[str, str] | None
|
when the query itself failed (podman missing, or |
dict[str, str] | None
|
erroring or hanging — e.g. on storage-lock contention with a |
dict[str, str] | None
|
concurrent build). Callers must not read a failure as "no containers": |
dict[str, str] | None
|
a status display that does so degrades every task to "not found" |
dict[str, str] | None
|
for as long as the runtime is busy (terok#1134). |
Source code in src/terok_sandbox/runtime/podman.py
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 | |
events(prefix)
¶
Stream container lifecycle events for containers named <prefix>-….
The push-based companion to
container_states:
a long-lived podman events subscription so a watcher reacts to a
container starting, dying or being removed instead of polling
podman ps on a clock. Returns a
PodmanEventStream the
caller iterates (typically on a worker thread) and close()s when
done. Backend-specific; not part of the
ContainerRuntime protocol.
Source code in src/terok_sandbox/runtime/podman.py
container_rw_sizes(prefix)
¶
Return {container_name: rw_bytes} for matching containers.
Single podman ps --size call — --size is expensive (overlay
diffs) but one bulk call beats N inspects. Backend-specific; not
part of the ContainerRuntime protocol.
Source code in src/terok_sandbox/runtime/podman.py
redact_env_args(cmd)
¶
Return a copy of cmd with sensitive -e KEY=VALUE args redacted.
Handles the two-arg form only (-e KEY=VALUE). Callers passing
sensitive values via single-arg forms (--env=...) must pre-redact.
Source code in src/terok_sandbox/runtime/podman.py
bypass_network_args(gate_port)
¶
Return podman network args for running without shield.
Replicates shield's normal networking (reachable host.containers.internal)
without nftables rules. Dangerous fallback — all egress is unfiltered.
Source code in src/terok_sandbox/runtime/podman.py
find_init_binary()
¶
Locate the init binary podman run --init would inject, or None.
Deliberately conservative: only the directories podman searches by
default are probed — claiming --init on a hunch (say, a PATH
hit podman itself would not honour) would fail the launch outright,
while a false negative merely degrades it. The probe has two
callers with one contract: Sandbox
launches pass --init only when it hits, and
terok-sandbox setup warns when it misses — a catatonit-less
host still works, its containers just take the full grace period
plus a force-kill on every stop. Hosts with a custom
helper_binaries_dir land on that same degraded-but-working
side.