Skip to main content

Module compose

Module compose 

Source
Expand description

The compose shell-out engine (04-01, RIG-01): runner seam, version check, LOCKED arg builders, and the LDJSON/array parsers — serde models out, no printing (the TUI rides the actions layer in Phase 6).

§The runner seam

ComposeRunner is the ONLY way any rig code spawns a process: actions and discovery take &dyn ComposeRunner, so every decision path is unit-testable against a scripted fake (no docker needed). The production DockerCompose shells out via tokio::process::Command (the workspace process feature — the one Phase 4 dependency change). run prefixes the compose subcommand; run_docker spawns the PLAIN docker CLI (volume ls / port attribution — no -p prefix, those are not compose project ops); run_streaming pipes stdout and forwards lines as they arrive (the logs -f follow shape, 04-02).

§LOCKED invocation shapes (research §Compose invocation shapes)

Every builder is a pure function whose exact output vector is unit-pinned — -p <resolved-name> EXPLICIT on every project op (Pitfall 8: no implicit directory-name projects), --project-directory always on resolve (Pitfall 8: .env loading is cwd-sensitive), --remove-orphans on up AND down (Pitfall 4), --wait-timeout explicit on up (Pitfall 3: healthchecks + image pulls).

§The two output conventions (research Pitfall 1)

ps/volume ls/docker ps emit ONE OBJECT PER LINE (LDJSON — parsed with a StreamDeserializer); config --format json emits a SINGLE doc (an object on current compose; an older array shape is tolerated by unwrapping the first element). BOTH conventions are fixture-pinned so the divergence can never regress into a naive from_str::<Vec<T>>.

Structs§

ComposeOutput
One completed docker … invocation: captured output + exit code. A spawn FAILURE (no docker binary) is code: 127 with the spawn error in stderr — one error path, no io::Error leakage.
DockerCompose
Production ComposeRunner: shells out to the real docker binary via tokio::process::Command.
DockerPsEntry
One docker ps --filter publish= row, reduced to attribution: the container’s (first) name and its compose project label, when it has one. docker ps JSON differs from compose ps JSON (Names plural, Labels sometimes a "k=v,k=v" string instead of a map) — both shapes are tolerated and fixture-pinned.
PortMapping
One published-port mapping from a resolved compose config (container target → host published).
Publisher
One docker compose ps publisher row (live-captured field names).
ServiceStatus
One docker compose ps service row (live-captured field names; Health/Publishers optional — services without healthchecks or ports omit them).
VolumeEntry
One docker volume ls row (only the name is consumed by status).

Traits§

ComposeRunner
The process seam for everything rig-related. Actions NEVER spawn processes directly — they script this trait (the GatewayApi precedent), which is what makes the whole family testable without docker.

Functions§

check_output
Map a completed invocation: exit 0 → the stdout str; nonzero → CoreError::Rig carrying the stderr tail (last [STDERR_TAIL_LINES] lines — research Pitfall 3).
compose_version
Verify docker compose answers with major version ≥ 2 (the v1 docker-compose Python binary never answers to docker compose — absence IS the install-hint case). Returns the version string (e.g. "5.1.2").
config_args
The resolve step (research Pattern 1): docker compose -f <file> --project-directory <dir> config --format json. --project-directory is ALWAYS explicit — .env (and thus COMPOSE_PROJECT_NAME) loading is cwd-sensitive (Pitfall 8).
docker_ps_publish_args
docker ps --filter publish=<port> --format json — host-port occupancy with attribution (research Pattern 3); also a PLAIN-docker shape via ComposeRunner::run_docker.
down_args
down: stop + remove containers/networks; --remove-orphans always (Pitfall 4); -v (named+anonymous volume deletion) only for the reset teardown half (04-02).
logs_args
logs (human-form passthrough by design — the streaming exception when --follow; wired by 04-02’s rig_logs via run one-shot / run_streaming follow). Invocation shape LOCKED from day one.
parse_config
Parse docker compose config --format json output into a RigPlan skeleton: .name is THE identity truth (honors the rig’s own .env COMPOSE_PROJECT_NAME), services are the service map’s keys, port_mappings the collected target→published pairs, volumes the volume map’s keys. Tolerant where compose is shape-shifty (published arrives as string OR number; the doc may be a bare object — current compose — or a single-element array — older builds); loud where identity is at stake (no .name → error, never an implicit directory-name project).
parse_docker_ps_ldjson
Parse docker ps --format json LDJSON into DockerPsEntry rows (name + compose-project attribution only).
parse_ps_ldjson
Parse docker compose ps --format json LDJSON into ServiceStatus rows.
parse_volume_ls_ldjson
Parse docker volume ls --format json LDJSON (research Pitfall 1).
ps_args
ps as LDJSON (research Pitfall 1): one object per service.
reset_preview
The reset preview (04-02, RIG-01): the named-volume names rig reset’s down -v half will remove, reported in the result data so agents see WHAT reset took before/as it acts. Label-filtered at the docker layer (volume_ls_args) and name-filtered here — only <project>_-prefixed volumes are reset’s to take (defense in depth; research Pitfall 4 shape: Name + Labels).
up_args
up (research LOCKED shape): explicit -p <name> (never an implicit directory-name project), detached + --wait with an EXPLICIT timeout (Pitfall 3: --wait blocks on healthchecks and image pulls), --remove-orphans.
volume_ls_args
docker volume ls (04-01 status / 04-02 reset preview): PLAIN docker CLI — no compose subcommand, no -p prefix (volumes are labeled, not project-scoped, at this layer). Invoked via ComposeRunner::run_docker, which spawns docker, NOT docker compose, for exactly this shape.