diff --git a/Cargo.toml b/Cargo.toml index 55f4efc..a4a9b71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,10 @@ tracing = "0.1" # We support both versions cap-std-ext = ">= 2.0, <= 3.0" +[dev-dependencies] +bytes = "1.5" +clap = { version = "4.4", features = ["derive"] } + [lib] path = "src/imageproxy.rs" diff --git a/examples/client.rs b/examples/client.rs new file mode 100644 index 0000000..de484e4 --- /dev/null +++ b/examples/client.rs @@ -0,0 +1,84 @@ +use std::io::Write; + +use anyhow::Result; +use clap::Parser; +use oci_spec::image::ImageManifest; +use tokio::io::AsyncReadExt; + +#[derive(clap::Parser, Debug)] +struct GetMetadataOpts { + /// The skopeo-style transport:image reference + reference: String, +} + +#[derive(clap::Parser, Debug)] +struct GetBlobOpts { + /// The skopeo-style transport:image reference + reference: String, + + /// The digest of the target blob to fetch + digest: String, + + /// The size of the blob to fetch + size: u64, +} + +/// Simple program to greet a person +#[derive(clap::Parser, Debug)] +#[command(version, about, long_about = None)] +enum Opt { + GetMetadata(GetMetadataOpts), + GetBlob(GetBlobOpts), +} + +#[derive(serde::Serialize, Debug)] +struct Metadata { + digest: String, + manifest: ImageManifest, +} + +async fn get_metadata(o: GetMetadataOpts) -> Result<()> { + let proxy = containers_image_proxy::ImageProxy::new().await?; + let img = proxy.open_image(&o.reference).await?; + let (digest, manifest) = proxy.fetch_manifest(&img).await?; + let metadata = Metadata { digest, manifest }; + serde_json::to_writer_pretty(&mut std::io::stdout().lock(), &metadata)?; + Ok(()) +} + +async fn get_blob(o: GetBlobOpts) -> Result<()> { + let proxy = containers_image_proxy::ImageProxy::new().await?; + let img = proxy.open_image(&o.reference).await?; + let (mut blob, driver) = proxy.get_blob(&img, &o.digest, o.size).await?; + + let mut stdout = std::io::stdout().lock(); + let reader = async move { + let mut buffer = [0u8; 8192]; + loop { + let n = blob.read(&mut buffer).await?; + if n == 0 { + return anyhow::Ok(()); + } + stdout.write_all(&buffer[..n])?; + } + }; + + let (a, b) = tokio::join!(reader, driver); + a?; + b?; + Ok(()) +} + +async fn run() -> Result<()> { + match Opt::parse() { + Opt::GetMetadata(o) => get_metadata(o).await, + Opt::GetBlob(o) => get_blob(o).await, + } +} + +#[tokio::main(flavor = "current_thread")] +async fn main() { + if let Err(e) = run().await { + eprintln!("{:#}", e); + } +}