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).
env
property
¶
Environment recorded on the container when it was created.
Reads the KEY=value list from podman inspect .Config.Env,
so a caller can compare what the container was told against what the
host offers today. Empty on an absent container or any inspect
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
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 | |
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
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 | |
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
unshielded_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
init_binary_unavailable(exc)
¶
Identify Podman's missing default helper before container creation.
Explicit broken init paths and unrelated launch errors must not silently disable init. Unknown diagnostics remain failures.