Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/draupnir

.nornir/run-to-completion-design.md

draupnir run_to_completion — the run-to-completion container seam

Added draupnir 0.1.4 (additive). This is the missing seam that completes the container-engine consolidation (.nornir/container-consolidation-plan.md): a single call that fires up a container, waits for it to exit, collects its logs, and returns an exit-code-aware result — the run-to-completion analogue of how a VM / container boot already delegates through draupnir::Boot.

Why it exists

The chain is app -> jera (job) -> draupnir (provision). KVM and container boot were already unified: jera's DraupnirVmBootRunner / DraupnirContainerBootRunner drive draupnir::Boot + ContainerControl, one engine. But jera kept a second, duplicate bollard engine (BollardEngine) alive for exactly one reason: its run_container is a run-to-completion job (create -> start -> wait for exit -> capture stdout/stderr + exit code -> remove) and draupnir offered only boot + lifecycle, never that one-shot. nornir::jobs::run_container consumes jera's engine, so the duplicate could not be removed.

run_to_completion closes that gap. jera's run_container (and nornir's above it) can repoint onto this seam, after which BollardEngine / BollardBootRunner / the ContainerEngine trait become dead code and are deleted — one engine, not two. (The repoint itself is jera/nornir work, owned by those repos' agents — this pass only provides + documents the draupnir API.)

The API (the only design — no alternatives)

pub struct RunOutcome {
    pub exit_code: Option<i64>,   // Some(0) = clean; None = gone before a code was read
    pub stdout: Vec<String>,      // captured stdout lines, in order
    pub stderr: Vec<String>,      // captured stderr lines, in order
}

pub struct RunOptions {
    pub timeout: Option<Duration>,  // None = wait forever (jera's wait_container parity)
    pub poll_interval: Duration,    // default 200ms
}

// The generic, mock-testable seam — drives a live ContainerBoot in production
// AND a mock in a unit test with no daemon.
pub fn run_to_completion<B>(backend: &B, spec: &BootSpec, opts: &RunOptions) -> Result<RunOutcome>
where B: Boot + ContainerControl;

// The ergonomic production entry point (forwards to the free fn with `self`).
impl ContainerBoot {
    pub fn run_to_completion(&self, spec: &BootSpec, opts: &RunOptions) -> Result<RunOutcome>;
}

Design decisions, and why:

Tests (all no-daemon)

src/container.rs:

Perf / bench

N/A — no bench added, by design. run_to_completion is a poll loop gated on container start/exit + log-stream IO; there is no compute hot path to guard, and a real measurement would need a live podman socket on the bencher (Loki), which it does not have. Forcing a bench here would measure daemon latency, not draupnir. Noted in bencher_agents_problems.md.