mirror of
https://github.com/vhaudiquet/ecrou.git
synced 2026-11-12 22:22:44 +00:00
feat(cli): add ecrou pull
Fetch or refresh an image's cache entry without booting it. The command is the CLI face of the run/pull split: runs never contact a registry on a cached image, pull does - reporting the entry path and whether anything transferred. --no-cache forces a redownload. SPEC documents the command and the run/pull trust model.
This commit is contained in:
1 parent
12a417645d
commit
b4dafccae7
3 files changed
+68
-8
No files matched your search
@@ -243,9 +243,12 @@ for narration, `Progress` for long operations.
|
|||||||
### Rootfs lifecycle (ported)
|
### Rootfs lifecycle (ported)
|
||||||
|
|
||||||
`RootfsCache::new(dir)` / `default_dir()`, `prepare`,
|
`RootfsCache::new(dir)` / `default_dir()`, `prepare`,
|
||||||
`prepare_provisioned`, `persist`, `PrepareRequest`, `PreparedRootfs`:
|
`prepare_provisioned`, `pull`, `persist`, `PrepareRequest`,
|
||||||
ecr's semantics verbatim, including digest freshness on `:latest` and
|
`PreparedRootfs`, `Pulled`: `prepare` is the boot path — a present
|
||||||
the `.provisioned` hot-cell marker. The per-run scratch copy is
|
entry is trusted, the registry is never contacted on a hit, a cold
|
||||||
|
cache downloads. `pull` is the explicit freshness check (unpinned
|
||||||
|
tags re-verified, moved digests re-downloaded, nothing extracted).
|
||||||
|
Plus the `.provisioned` hot-cell marker. The per-run scratch copy is
|
||||||
`PreparedRootfs` as ecr already builds it. See ecr's SPEC for the
|
`PreparedRootfs` as ecr already builds it. See ecr's SPEC for the
|
||||||
reference description; differences are the cache directory and the
|
reference description; differences are the cache directory and the
|
||||||
absent kernel entries only.
|
absent kernel entries only.
|
||||||
@@ -296,9 +299,18 @@ composable. A boot failure leaves the files absent.
|
|||||||
```
|
```
|
||||||
ecrou run [OPTIONS] <IMAGE> [-- COMMAND...]
|
ecrou run [OPTIONS] <IMAGE> [-- COMMAND...]
|
||||||
ecrou provision [OPTIONS] <IMAGE> [-- COMMAND...]
|
ecrou provision [OPTIONS] <IMAGE> [-- COMMAND...]
|
||||||
|
ecrou pull [OPTIONS] <IMAGE>
|
||||||
ecrou probe
|
ecrou probe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`pull` fetches or refreshes the image's cache entry without booting
|
||||||
|
it: it re-resolves floating references (distro aliases like
|
||||||
|
`latest`/`lts`/`edge`, unpinned OCI tags against the registry
|
||||||
|
digest) and downloads only when the entry is absent or has moved.
|
||||||
|
It is the freshness operation — see the trust model below for how
|
||||||
|
`run` and `pull` divide the work. Local archives are rejected:
|
||||||
|
a file on disk has nothing to fetch.
|
||||||
|
|
||||||
The image is a distro name (`alpine`, `ubuntu:noble`), an OCI
|
The image is a distro name (`alpine`, `ubuntu:noble`), an OCI
|
||||||
reference (`ghcr.io/org/image:tag`, `ubuntu@sha256:…`,
|
reference (`ghcr.io/org/image:tag`, `ubuntu@sha256:…`,
|
||||||
`repo:tag@sha256:…` — the digest wins over a co-present tag, as in
|
`repo:tag@sha256:…` — the digest wins over a co-present tag, as in
|
||||||
@@ -395,11 +407,17 @@ stop:
|
|||||||
publishes next to them. When the checksum artifact cannot be fetched
|
publishes next to them. When the checksum artifact cannot be fetched
|
||||||
the download proceeds unverified with a warning instead of
|
the download proceeds unverified with a warning instead of
|
||||||
hard-failing. That is a deliberate trade, not an oversight.
|
hard-failing. That is a deliberate trade, not an oversight.
|
||||||
- The cache trusts its own entries. Once written, an entry is never
|
- The cache trusts its own entries, and `run` never contacts a
|
||||||
re-verified, except OCI `:latest` tags, which are re-checked against
|
registry on a cache hit: a present entry is booted as-is, the way
|
||||||
the registry digest on every run. Digest pins, pinned tags and
|
`docker run` uses the local image. The freshness operation is
|
||||||
distro tarballs are assumed immutable; `--no-cache` and the
|
`pull` (the CLI command and the library's `RootfsCache::pull`):
|
||||||
digest-changed path are the escape hatches.
|
it re-checks unpinned OCI tags against the registry digest and
|
||||||
|
re-resolves floating distro aliases, downloading only what moved —
|
||||||
|
`docker pull` semantics. Digest pins, pinned tags and numbered
|
||||||
|
distro tarballs are immutable to both; on a network error `pull`
|
||||||
|
keeps the cached image with a warning. `--no-cache` is the
|
||||||
|
force-redownload escape hatch. The trade: a stale `:latest` boots
|
||||||
|
until someone pulls, exactly as in docker.
|
||||||
- Concurrent runs of the same image are safe, not efficient: each
|
- Concurrent runs of the same image are safe, not efficient: each
|
||||||
writes its own partial file and the last atomic rename wins, so two
|
writes its own partial file and the last atomic rename wins, so two
|
||||||
racers download twice rather than corrupt anything.
|
racers download twice rather than corrupt anything.
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ pub enum Command {
|
|||||||
#[arg(last = true)]
|
#[arg(last = true)]
|
||||||
command: Vec<String>,
|
command: Vec<String>,
|
||||||
},
|
},
|
||||||
|
/// 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
|
||||||
|
/// entry is absent or has moved. `run` never contacts the
|
||||||
|
/// registry on a cached image — this is the refresh operation.
|
||||||
|
Pull {
|
||||||
|
/// Distribution name or OCI image reference
|
||||||
|
image: String,
|
||||||
|
/// Ignore the cache: re-download even when the entry is
|
||||||
|
/// present and current
|
||||||
|
#[arg(long)]
|
||||||
|
no_cache: bool,
|
||||||
|
},
|
||||||
/// Report what a run would find on this host
|
/// Report what a run would find on this host
|
||||||
Probe,
|
Probe,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ mod logger;
|
|||||||
mod progress;
|
mod progress;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use ecrou::cache::RootfsCache;
|
||||||
|
use ecrou::rootfs::PrepareRequest;
|
||||||
use ecrou::vm::RunOptions;
|
use ecrou::vm::RunOptions;
|
||||||
use ecrou::{LogLevel, NoProgress, Progress};
|
use ecrou::{LogLevel, NoProgress, Progress};
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ fn main() -> std::process::ExitCode {
|
|||||||
no_cache,
|
no_cache,
|
||||||
command,
|
command,
|
||||||
} => cmd_provision(image, spec, layer, no_cache, &command, cli.verbose),
|
} => cmd_provision(image, spec, layer, no_cache, &command, cli.verbose),
|
||||||
|
Command::Pull { image, no_cache } => cmd_pull(&image, no_cache, cli.verbose),
|
||||||
Command::Probe => cmd_probe(),
|
Command::Probe => cmd_probe(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +223,32 @@ fn cmd_run(args: &RunArgs, cli_verbose: bool) -> std::process::ExitCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pull an image into the cache without booting it.
|
||||||
|
fn cmd_pull(image: &str, no_cache: bool, _cli_verbose: bool) -> std::process::ExitCode {
|
||||||
|
let progress: &dyn Progress = &TermProgress::new();
|
||||||
|
let dir = match RootfsCache::default_dir() {
|
||||||
|
Ok(dir) => dir,
|
||||||
|
Err(err) => return fail(&anyhow::Error::new(err)),
|
||||||
|
};
|
||||||
|
let cache = RootfsCache::new(dir);
|
||||||
|
|
||||||
|
let mut request = PrepareRequest::new(image.to_owned());
|
||||||
|
request.no_cache = no_cache;
|
||||||
|
|
||||||
|
match cache.pull(&request, progress) {
|
||||||
|
Ok(pulled) => {
|
||||||
|
if pulled.downloaded {
|
||||||
|
println!("pulled {image}");
|
||||||
|
} else {
|
||||||
|
println!("{image} is up to date");
|
||||||
|
}
|
||||||
|
println!(" {}", pulled.cache_path.display());
|
||||||
|
std::process::ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
Err(err) => fail(&anyhow::Error::new(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn cmd_probe() -> std::process::ExitCode {
|
fn cmd_probe() -> std::process::ExitCode {
|
||||||
use ecrou::krun;
|
use ecrou::krun;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user