Skip to main content

isothrum_runner/
lib.rs

1//! The Isothrum runner: executes the run manifest Isoloom writes next to a target's generated
2//! files (`.isoloom/<target>/run.json`), and reads nothing else of them.
3//!
4//! The manifest holds the native commands in order, the documented driver actions, readiness,
5//! outputs, check runners, and the slots inputs and secrets travel through. The runner adds what
6//! only it can know: the run's name and private state, one operation per run at a time, the
7//! values of params and secrets, and the event stream an embedder (the Cyber CTF launcher, the
8//! Isothrum desktop app, the `isothrum` command line) shows. It sits between Isoloom (whose
9//! `isoloom-manifest` crate is the contract) and those embedders, and depends on no UI.
10//!
11//! # Example
12//!
13//! Opening a manifest reads and checks it, and nothing runs yet:
14//!
15//! ```
16//! use isothrum_runner::{RunName, Runner, RunnerConfig};
17//!
18//! let home = tempfile::tempdir()?;
19//! let dir = tempfile::tempdir()?;
20//! let manifest = dir.path().join("run.json");
21//! std::fs::write(&manifest, include_str!("../tests/fixtures/flag-portal.run.json"))?;
22//!
23//! let runner = Runner::new(RunnerConfig::new(home.path()))?;
24//! let run = runner.open(&manifest, Some(RunName::new("demo")?))?;
25//! assert_eq!(run.manifest().environment.name.as_str(), "flag-portal");
26//! assert_eq!(run.name().as_str(), "demo");
27//! assert!(runner.runs()?.is_empty(), "no operation has run");
28//! # Ok::<(), Box<dyn std::error::Error>>(())
29//! ```
30//!
31//! Running it takes the tools it names (here Docker), and an event sink:
32//!
33//! ```no_run
34//! # async fn demo(run: &isothrum_runner::Running<'_>) -> isothrum_runner::Result<()> {
35//! use isothrum_runner::{JsonLines, Park, RunOptions};
36//!
37//! let events = JsonLines::new(std::io::stderr());
38//! let opts = RunOptions::new().with_param("ISOLOOM_PUBLISH_FIXED", "1");
39//! let started = run.up(&opts, &events).await?;
40//! let report = run.check(&opts, &events).await?;
41//! run.park(Park::Shutdown, &events).await?;
42//! run.resume(&opts, &events).await?;
43//! run.down(&opts, &events).await?;
44//! # let _ = (started, report);
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! # Ports
50//!
51//! Every outside effect goes through a port the embedder can replace: [`ProcessRunner`] (the
52//! tools), [`HostPorts`] (the host's ports), [`Clock`] (the time), [`Events`] (where events go)
53//! and [`Hooks`] (the embedder's sidecars). They are open traits on purpose, since embedders
54//! implement them; the `fake` feature has test doubles for the first three.
55//!
56//! # Deliveries
57//!
58//! Params and slot values reach the tools only through the manifest's deliveries, never on a
59//! command line: `env` (a step's variables), `tf_var` (`TF_VAR_<var>`, a `map_entry` making one
60//! JSON object), `state_file` (an owner-only file in the run's state folder, a Terraform variable
61//! file or `NAME=value` lines, written at `up`, kept for the destroy, removed by a successful
62//! `down`), and `guest` over SSH, Isoloom's delivery into a machine for a cloud with no secret
63//! store (DigitalOcean, Linode): once the step it names (else every `up` step) succeeded, one
64//! SSH session per machine, to the outputs `address_<machine>` (`-` as `_`) and
65//! `ssh_user_<machine>` (else `ssh_user`), runs a fixed script as root that keeps each value
66//! owner-only under `/var/lib/isoloom/secrets/<slot name>` and then touches
67//! `/var/lib/isoloom/secrets/.delivered`, which the machine's set-up waits for; the values travel
68//! base64-encoded on its standard input, retried while SSH can't connect (up to 15 minutes).
69//!
70//! # Concurrency
71//!
72//! The runner is **async**, on Tokio, chosen once for the whole crate: every tool runs as an async
73//! process with a time limit (a step's `timeout_s`, else an hour; 25 s for a probe; two minutes
74//! for an action's own Docker calls; ten for a hook), and dropping an operation's future ends the
75//! process tree it started. Operations on one run are serialized; operations on different runs
76//! run side by side. One exception to "no blocking call in async code": the run's state files (a
77//! record, a lock, the pinned ports, the event log) are a few KB on the local disk, so they are
78//! read and written inline rather than through `spawn_blocking`.
79//!
80//! # Errors and tracing
81//!
82//! Every fallible call returns [`Result`], whose [`Error`] has a stable [`ErrorCode`]; an
83//! operation also reports its failure as its last event. The runner logs through `tracing` (spans
84//! `run`, `step` and `driver`, events for each process), never by printing.
85
86mod adopt;
87mod archive;
88mod checks;
89mod clock;
90mod container;
91mod driver;
92mod encoding;
93mod error;
94mod events;
95mod executor;
96mod future;
97#[cfg(feature = "fuzzing")]
98#[doc(hidden)]
99pub mod fuzzing;
100mod guest;
101mod hooks;
102mod in_vm;
103mod manifest;
104mod name;
105mod net;
106mod options;
107mod outputs;
108mod params;
109pub mod process;
110mod ready;
111mod reap;
112mod relay;
113mod run;
114mod runner;
115mod scope;
116mod secrets;
117mod slot;
118mod ssh;
119mod state;
120mod status;
121mod store;
122#[cfg(test)]
123mod testing;
124mod tools;
125mod vars;
126
127pub use adopt::Adoption;
128pub use checks::{CheckReport, CheckResult};
129#[cfg(any(test, feature = "fake"))]
130pub use clock::FixedClock;
131pub use clock::{Clock, SystemClock, rfc3339};
132pub use error::{Error, ErrorCode, Result};
133pub use events::{EVENTS_VERSION, Event, EventKind, Events, JsonLines, ReadyState, Recorder, StepState, Stream, WarningCode, events_schema};
134pub use future::BoxFuture;
135pub use hooks::{HookError, Hooks, RunView};
136pub use isoloom_manifest;
137pub use manifest::{LoadedManifest, load_manifest};
138pub use name::RunName;
139#[cfg(any(test, feature = "fake"))]
140pub use net::FakePorts;
141pub use net::{HeldPort, HostPorts, LoopbackPorts};
142pub use options::{Occupied, RunOptions};
143pub use process::{ProcessRunner, ProcessSpec, SystemProcesses};
144pub use reap::{Orphans, Place, Tagged};
145pub use run::{AttachReport, Running};
146pub use runner::{Runner, RunnerConfig, this_host};
147pub use secrets::{SecretValue, Secrets};
148pub use state::{Op, Park, RunState};
149pub use status::{MachineState, MachineStatus, NetworkView, OutputValue, PublishedPort, ShellCommand, Started, Status};
150pub use store::{LastOp, RunRecord};