Specify and plan M3b: the runner and the tools
A draft spec for the owner's review and 13 offline tasks with their given tests: shared tool arguments and host rules in proto, the sealed fetch target (M3a finding 14), the toolkit tools and SOCKS5 egress proxy, and brokerd's [runner], podman argument lists, runtime and proxy lifecycle. Each task's tests were run against a reference at that task's end state (560 to 638 tests, clippy clean); the reference is not in the repository. Adds the runner-unavailable runbook entry and tip T23 (ETXTBSY in script tests). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# M3b task 10: the `podman` argument lists
|
||||
|
||||
**Branch:** `m3b` (run `git switch m3b`; `git status --short` must be empty, otherwise stop)
|
||||
**Commit subject:** `brokerd: the podman argument lists`
|
||||
|
||||
## Goal
|
||||
|
||||
Build, without running anything, the exact `podman` arguments for one call's container, and for
|
||||
`http_fetch`'s egress proxy. Every argument is its own `OsString`: nothing is ever passed through a
|
||||
shell, and the tool's arguments go on standard input, never on the command line. The lists are
|
||||
tested as golden files, one argument per line. Spec section 6.
|
||||
|
||||
The container's name says whose call it is, so `RunSpec` gains the session and the call.
|
||||
|
||||
## Files
|
||||
|
||||
- Copy: `crates/brokerd/tests/podman_args.rs` and the six files
|
||||
`crates/brokerd/tests/fixtures/podman/*.args`
|
||||
- Create: `crates/brokerd/src/podman.rs`
|
||||
- Modify: `crates/brokerd/src/runner.rs`, `crates/brokerd/src/lib.rs` (`pub mod podman;`),
|
||||
`docs/implementer-log.md`
|
||||
|
||||
## `RunSpec` gains two fields
|
||||
|
||||
In `runner.rs`, `RunSpec` gets `session: SessionId` and `call: CallId` as its **first** two
|
||||
fields (private, like the others), with getters:
|
||||
|
||||
```rust
|
||||
pub fn session(&self) -> &SessionId;
|
||||
pub fn call(&self) -> CallId;
|
||||
```
|
||||
|
||||
`run` fills them from `decision.request().session.clone()` and `decision.request().call`. The
|
||||
`compile_fail` doctest at the top of `runner.rs` builds a `RunSpec` with a struct literal: add the
|
||||
two fields to it, first, so it still fails **only** because the fields are private:
|
||||
|
||||
```rust
|
||||
//! session: proto::SessionId::new("s1").unwrap(),
|
||||
//! call: proto::CallId(1),
|
||||
```
|
||||
|
||||
## `podman.rs`
|
||||
|
||||
```rust
|
||||
pub const EGRESS_MOUNT: &str = "/run/egress";
|
||||
pub const EGRESS_SOCKET: &str = "/run/egress/egress.sock";
|
||||
pub const TOOLKIT: &str = "/bin/toolkit";
|
||||
|
||||
/// "boxmaker-<session>-<call>-<n>"
|
||||
pub fn container_name(session: &SessionId, call: CallId, n: u64) -> String;
|
||||
|
||||
/// The tool's container. `egress` is the call's egress directory, for `http_fetch` only.
|
||||
pub fn tool_args(spec: &RunSpec, runner: &Runner, name: &str, egress: Option<&Path>) -> Vec<OsString>;
|
||||
|
||||
/// The egress proxy's container.
|
||||
pub fn egress_args(runner: &Runner, name: &str, dir: &Path, hosts: &[String]) -> Vec<OsString>;
|
||||
```
|
||||
|
||||
`tool_args`, in this order:
|
||||
|
||||
```
|
||||
run --rm -i --name=<name> --label=boxmaker=tool --network=none
|
||||
--read-only --cap-drop=all --security-opt=no-new-privileges --userns=keep-id
|
||||
--pids-limit=<runner.pids> --memory=<runner.memory>
|
||||
--tmpfs=/tmp:rw,size=64m,mode=1777
|
||||
--volume=<path>:<path>:ro (or :rw when writable) one per spec.mounts(), in order
|
||||
--volume=<egress dir>:/run/egress:rw only when `egress` is Some
|
||||
<runner.image> /bin/toolkit <spec.tool().as_str()>
|
||||
```
|
||||
|
||||
`egress_args`, in this order:
|
||||
|
||||
```
|
||||
run -d --rm --name=<name>-egress --label=boxmaker=egress --network=<runner.egress_network>
|
||||
--read-only --cap-drop=all --security-opt=no-new-privileges --userns=keep-id
|
||||
--pids-limit=64 --memory=128m
|
||||
--volume=<dir>:/run/egress:rw
|
||||
<runner.image> /bin/toolkit egress-proxy --socket /run/egress/egress.sock --allow <hosts joined with ",">
|
||||
```
|
||||
|
||||
The six hardening flags (`--read-only` to `--memory=…`) are the same in both, so write them once in
|
||||
a private function. Build the `--volume=<dir>:…` argument as an `OsString` with `push`, so a
|
||||
directory need not be UTF-8 (`dir.as_os_str()`), not with `format!` on `display()`.
|
||||
|
||||
## Steps
|
||||
|
||||
- [ ] **1. Copy.** `git switch m3b`, then
|
||||
`cp docs/plans/M3b/files/crates/brokerd/tests/podman_args.rs crates/brokerd/tests/`,
|
||||
`mkdir -p crates/brokerd/tests/fixtures/podman`,
|
||||
`cp docs/plans/M3b/files/crates/brokerd/tests/fixtures/podman/*.args crates/brokerd/tests/fixtures/podman/`
|
||||
- [ ] **2. See it fail.** `cargo test -p brokerd --test podman_args`. Expected: it does not
|
||||
compile.
|
||||
- [ ] **3. Write the code.** Run `cargo fmt --all`.
|
||||
- [ ] **4. See it pass.** `cargo test -p brokerd --test podman_args --test runner`. Expected: 7
|
||||
and 8 passed. `cargo test -p brokerd --doc`: every doctest passes.
|
||||
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
|
||||
- [ ] **6. Log and commit.** `git add crates/brokerd docs/implementer-log.md && git commit`
|
||||
|
||||
## Done when
|
||||
|
||||
- `cargo test -p brokerd` and `cargo test -p brokerd --doc` pass; `make gate` prints `gate: ok`.
|
||||
|
||||
## Stop and report if
|
||||
|
||||
- A golden file disagrees with the lists above: report both, do not edit the golden file.
|
||||
- Anything would put the tool's arguments (the command, the path, the URL) on `podman`'s command
|
||||
line.
|
||||
Reference in New Issue
Block a user