docs(cli): rewrite the command help

Each command keeps a crisp one-liner in the command list and carries
its contract in the long help: how an image reference resolves, what
provision's two shapes do, why pull exists (run never contacts a
registry on a cache hit), and what probe checks. Flag help states the
real constraints instead of the shortest noun phrase: the guest path
of a share must be absolute, the host environment is never inherited,
--capture is refused with --tty, --quiet silences engine and kernel
messages.
This commit is contained in:
vhaudiquet committed 2026-10-30 15:30:22 +01:00
1 parent 4aadc5d28a
commit 3d999b7be0
1 file changed
+55 -41
+55 -41
View File
@@ -6,71 +6,83 @@ use clap::{Args, Parser, Subcommand};
#[command( #[command(
name = "ecrou", name = "ecrou",
version, version,
about = "One-shot microVM environments on libkrun", about = "Run commands in disposable microVMs on libkrun",
after_help = "Examples:\n ecrou run -it alpine a shell in the machine\n ecrou run ./rootfs.tar.gz -- uname -a boot a local rootfs archive\n ecrou run -v $PWD:/workspace ubuntu -- make" after_help = "Examples:\n ecrou run -it alpine a shell in the machine\n ecrou run ubuntu -- uname -a one command; its exit code propagates\n ecrou run -v $PWD:/src ubuntu -- make share the working tree into the machine\n ecrou pull alpine fetch or refresh the cache entry\n ecrou probe report what a run would find on this host"
)] )]
pub struct Cli { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Command,
/// Print diagnostic messages /// Print diagnostics, including engine messages
#[arg(long, global = true)] #[arg(long, global = true)]
pub verbose: bool, pub verbose: bool,
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
pub enum Command { pub enum Command {
/// Run a command in a fresh microVM from an image /// Run a command in a fresh microVM
///
/// IMAGE is a distro name (alpine, ubuntu:noble), an OCI image
/// reference (ghcr.io/org/image:tag, ubuntu@sha256:...), or a
/// local rootfs archive (a path: ./rootfs.tar.gz). The machine
/// boots once for the command, streams its stdio, and the run
/// exits with the command's exit code; the machine is thrown
/// away. Persistence crosses a --volume share or it does not
/// happen.
Run(RunArgs), Run(RunArgs),
/// Bake a command into an image's cache entry: the command runs in /// Bake a provisioned layer over a base image
/// a machine over a freshly prepared rootfs, and the result is ///
/// persisted, so every later boot of the image carries what the /// Two shapes. --spec FILE describes the layer in checked-in YAML
/// command installed. Cached entries are reused as-is; --no-cache /// (base, packages, run); the layer is keyed by the file's
/// re-downloads and re-provisions. /// content, so the same spec never provisions twice and a changed
/// spec never reuses an old layer, and --layer NAME names it for
/// `run --layer NAME`. `provision IMAGE -- COMMAND` bakes the
/// command into the image's cache entry itself: every later boot
/// of that image carries what the command installed.
Provision { Provision {
/// Distribution name or OCI image reference; the spec's `base` /// Base image: distro name or OCI reference; not needed when
/// when omitted and the spec carries one /// the spec carries a base
image: Option<String>, image: Option<String>,
/// A provisioning spec file (YAML: base, packages, run) — the /// Provisioning spec file (YAML: base, packages, run); the
/// layer it describes is keyed by the file's content, so the /// layer it describes is keyed by the file's content
/// same spec never provisions twice and a changed spec never
/// reuses an old layer
#[arg(long)] #[arg(long)]
spec: Option<std::path::PathBuf>, spec: Option<std::path::PathBuf>,
/// Name the provisioned layer, so runs can boot it by name /// Name the provisioned layer, so `run --layer NAME` boots it
/// (`run --layer NAME`) without re-deriving anything
#[arg(long)] #[arg(long)]
layer: Option<String>, layer: Option<String>,
/// Ignore the cache: re-download the base and re-provision /// Ignore the cache: re-download the base and re-provision
#[arg(long)] #[arg(long)]
no_cache: bool, no_cache: bool,
/// The provisioning command. An argument vector after `--` is /// Provisioning command, baked into the image's cache entry
/// taken literally; a single quoted line runs through the /// (use with IMAGE, without --spec)
/// image's shell.
#[arg(last = true)] #[arg(last = true)]
command: Vec<String>, command: Vec<String>,
}, },
/// Fetch or refresh an image's cache entry without booting it: /// Fetch or refresh an image's cache entry without booting it
/// re-checks unpinned OCI tags against the registry and floating ///
/// distro aliases against the distro, downloading only when the /// Unpinned OCI tags are re-checked against the registry digest
/// entry is absent or has moved. `run` never contacts the /// and floating distro aliases (latest, lts, edge) re-resolved;
/// registry on a cached image — this is the refresh operation. /// only an absent or moved entry is downloaded. `run` never
/// contacts a registry on a cached image: this is the freshness
/// operation, docker-pull semantics.
Pull { Pull {
/// Distribution name or OCI image reference /// Distribution name or OCI image reference
image: String, image: String,
/// Ignore the cache: re-download even when the entry is /// Re-download even when the entry is present and current
/// present and current
#[arg(long)] #[arg(long)]
no_cache: bool, no_cache: bool,
}, },
/// Report what a run would find on this host /// Report what a run would find on this host
///
/// KVM working (device, API version, nested-virt state), the
/// engine (libkrun.so.1) loadable, and the image cache usable.
/// The support tool and the confinement diagnostic.
Probe, Probe,
} }
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub struct RunArgs { pub struct RunArgs {
/// Distribution name, OCI image reference, or a local rootfs /// Distro name, OCI image reference, or a local rootfs archive
/// archive (a path: ./rootfs.tar.gz, /tmp/rootfs.tar); not needed /// path (./rootfs.tar.gz); not needed with --layer
/// with `--layer`, which boots a provisioned cache entry
#[arg(required_unless_present = "layer")] #[arg(required_unless_present = "layer")]
pub image: Option<String>, pub image: Option<String>,
/// Keep the machine's stdin open: the host's stdin feeds the /// Keep the machine's stdin open: the host's stdin feeds the
@@ -83,33 +95,35 @@ pub struct RunArgs {
pub tty: bool, pub tty: bool,
/// Command to run in the machine; the image's default shell when /// Command to run in the machine; the image's default shell when
/// omitted. An argument vector after `--` is taken literally; a /// omitted. An argument vector after `--` is taken literally; a
/// single quoted line runs through the image's shell. /// single quoted line runs through the image's shell as `sh -c`.
#[arg(last = true)] #[arg(last = true)]
pub command: Vec<String>, pub command: Vec<String>,
/// Directory to share into the machine as HOST:GUEST[:ro] (repeatable) /// Share a host directory into the machine as HOST:GUEST[:ro]
/// (repeatable; the guest path must be absolute)
#[arg(short = 'v', long = "volume")] #[arg(short = 'v', long = "volume")]
pub volumes: Vec<String>, pub volumes: Vec<String>,
/// Guest memory size /// Guest memory size (bytes, or with a K/M/G suffix)
#[arg(short = 'm', long, default_value = "1G")] #[arg(short = 'm', long, default_value = "1G")]
pub memory: String, pub memory: String,
/// Guest working directory /// Working directory inside the machine
#[arg(short = 'w', long, default_value = "/")] #[arg(short = 'w', long, default_value = "/")]
pub workdir: String, pub workdir: String,
/// Guest environment KEY=VALUE (repeatable) /// Guest environment KEY=VALUE (repeatable; the host environment
/// is never inherited)
#[arg(short = 'e', long = "env")] #[arg(short = 'e', long = "env")]
pub env: Vec<String>, pub env: Vec<String>,
/// Suppress console noise /// Silence engine and kernel messages on the console
#[arg(long)] #[arg(long)]
pub quiet: bool, pub quiet: bool,
/// Capture the payload's stdout and stderr into files in this /// Capture the payload's stdout and stderr into DIR/stdout and
/// directory: DIR/stdout and DIR/stderr. Created when missing. /// DIR/stderr (incompatible with --tty; created when missing)
#[arg(long)] #[arg(long)]
pub capture: Option<std::path::PathBuf>, pub capture: Option<std::path::PathBuf>,
/// Ignore the cache and download a fresh image /// Ignore the cache: download the image fresh
#[arg(long)] #[arg(long)]
pub no_cache: bool, pub no_cache: bool,
/// Boot a named provisioned layer (see `provision --layer`) /// Boot a named provisioned layer (see provision --layer)
/// instead of resolving an image /// instead of an image
#[arg(long)] #[arg(long)]
pub layer: Option<String>, pub layer: Option<String>,
} }