Skip to content

Commit bb5d68d

Browse files
committed
podstorage: Add image pull with streaming progress via podman API
Add a podman_client module for pulling container images through podman's native libpod HTTP API with streaming per-blob download progress displayed via indicatif. Starts a transient `podman system service` against bootc's custom storage root (reusing bind_storage_roots and setup_auth helpers) and talks to it over a Unix socket. The response NDJSON stream is read line-by-line via AsyncBufReadExt. Also fix get_ensure_imgstore() to work on composefs-only systems by falling back to physical_root when ostree is not initialized. Factor setup_auth() out of new_podman_cmd_in() so both the CLI podman commands and the API service share the same auth setup. Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 3f904b8 commit bb5d68d

10 files changed

Lines changed: 709 additions & 45 deletions

File tree

Cargo.lock

Lines changed: 107 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,18 @@ clap_mangen = { version = "0.3.0" }
4646
# The Justfile will auto-detect these and bind-mount them into container builds.
4747
cfsctl = { git = "https://github.com/composefs/composefs-rs", rev = "2203e8f", package = "cfsctl", features = ["rhel9"] }
4848
fn-error-context = "0.2.1"
49+
futures-util = "0.3"
4950
hex = "0.4.3"
51+
http-body-util = "0.1"
52+
hyper = { version = "1", features = ["client", "http1"] }
53+
hyper-util = { version = "0.1", features = ["tokio"] }
5054
indicatif = "0.18.0"
5155
indoc = "2.0.5"
5256
libc = "0.2.154"
5357
log = "0.4.21"
5458
openssl = "0.10.72"
5559
owo-colors = { version = "4" }
60+
percent-encoding = "2"
5661
regex = "1.10.4"
5762
# For the same rationale as https://github.com/coreos/rpm-ostree/commit/27f3f4b77a15f6026f7e1da260408d42ccb657b3
5863
rustix = { "version" = "1", features = ["use-libc", "thread", "net", "fs", "system", "process", "mount"] }

crates/lib/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,16 @@ clap_complete = "4"
3737
clap_mangen = { workspace = true, optional = true }
3838
cfsctl = { workspace = true }
3939
fn-error-context = { workspace = true }
40+
futures-util = { workspace = true }
4041
hex = { workspace = true }
42+
http-body-util = { workspace = true }
43+
hyper = { workspace = true }
44+
hyper-util = { workspace = true }
4145
indicatif = { workspace = true }
4246
indoc = { workspace = true }
4347
libc = { workspace = true }
4448
openssl = { workspace = true }
49+
percent-encoding = { workspace = true }
4550
regex = { workspace = true }
4651
rustix = { workspace = true }
4752
serde = { workspace = true, features = ["derive"] }

crates/lib/src/cli.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,11 @@ pub(crate) enum ImageCmdOpts {
492492
#[clap(allow_hyphen_values = true)]
493493
args: Vec<OsString>,
494494
},
495-
/// Wrapper for `podman image pull` in bootc storage.
495+
/// Pull image(s) into bootc storage.
496496
Pull {
497-
#[clap(allow_hyphen_values = true)]
498-
args: Vec<OsString>,
497+
/// Image references to pull (e.g. quay.io/myorg/myimage:latest)
498+
#[clap(required = true)]
499+
images: Vec<String>,
499500
},
500501
/// Wrapper for `podman image push` in bootc storage.
501502
Push {
@@ -1870,8 +1871,11 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
18701871
ImageCmdOpts::Build { args } => {
18711872
crate::image::imgcmd_entrypoint(imgstore, "build", &args).await
18721873
}
1873-
ImageCmdOpts::Pull { args } => {
1874-
crate::image::imgcmd_entrypoint(imgstore, "pull", &args).await
1874+
ImageCmdOpts::Pull { images } => {
1875+
for image in &images {
1876+
imgstore.pull_with_progress(image).await?;
1877+
}
1878+
Ok(())
18751879
}
18761880
ImageCmdOpts::Push { args } => {
18771881
crate::image::imgcmd_entrypoint(imgstore, "push", &args).await

crates/lib/src/deploy.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -540,15 +540,12 @@ pub(crate) async fn prepare_for_pull_unified(
540540
&imgref.transport
541541
);
542542

543-
// Pull the image to bootc storage using the same method as LBIs
544-
// Show a spinner since podman pull can take a while and doesn't output progress
545-
let pull_msg = format!("Pulling {} to bootc storage", &image_ref_str);
546-
async_task_with_spinner(&pull_msg, async move {
547-
imgstore
548-
.pull(&image_ref_str, crate::podstorage::PullMode::Always)
549-
.await
550-
})
551-
.await?;
543+
// Pull the image into bootc containers-storage with per-layer
544+
// download progress via the podman native API.
545+
imgstore
546+
.pull_with_progress(&image_ref_str)
547+
.await
548+
.context("Pulling image into bootc containers-storage")?;
552549

553550
// Now create a containers-storage reference to read from bootc storage
554551
tracing::info!("Unified pull: now importing from containers-storage transport");

crates/lib/src/image.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ pub(crate) const IMAGE_DEFAULT: &str = "localhost/bootc";
2626
/// Check if an image exists in the default containers-storage (podman storage).
2727
///
2828
/// TODO: Using exit codes to check image existence is not ideal. We should use
29-
/// the podman HTTP API via bollard (<https://lib.rs/crates/bollard>) or similar
30-
/// to properly communicate with podman and get structured responses. This would
31-
/// also enable proper progress monitoring during pull operations.
29+
/// the podman native libpod HTTP API to properly communicate with podman and
30+
/// get structured responses.
3231
async fn image_exists_in_host_storage(image: &str) -> Result<bool> {
3332
use tokio::process::Command as AsyncCommand;
3433
let mut cmd = AsyncCommand::new("podman");

crates/lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ mod lsm;
8686
pub(crate) mod metadata;
8787
mod parsers;
8888
mod podman;
89+
pub(crate) mod podman_client;
8990
mod podstorage;
9091
mod progress_jsonl;
9192
mod reboot;

0 commit comments

Comments
 (0)