mod common; use clap::Parser; use common::new_docker; use futures::StreamExt; use std::path::PathBuf; #[derive(Parser)] pub struct Opts { #[command(subcommand)] subcmd: Cmd, } #[derive(Parser)] enum Cmd { /// Build an image. Build { /// A path to the directory containing Dockerfile for the image. path: PathBuf, #[cfg(feature = "par-compress")] #[arg(short, long)] /// Use multithreaded compression algorithm multithread: bool, #[arg(default_value = "latest")] tag: String, }, /// Delete an image. Delete { image: String, #[arg(short, long)] force: bool, #[arg(long)] noprune: bool, }, /// Export an image as a tar archive. Export { image: String, }, /// Inspect an image. Inspect { image: String, }, Import { path: PathBuf, }, /// List existing images. List { #[arg(long, short)] /// Show all images. By default only final layer images are shown. all: bool, }, /// Pull an image from image registry. Pull { /// The name or id of the image to pull. image: String, /// Username in case authentication is required. username: Option, /// Password in case authentication is required. password: Option, }, /// Search for an image. Search { image: String, }, Tag { /// Repository containing the image to tag. repo: String, /// The name or id of the image to tag. image: String, tag: String, }, Prune, } #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); let docker = new_docker()?; let opts: Opts = Opts::parse(); match opts.subcmd { Cmd::Build { path, tag, #[cfg(feature = "par-compress")] multithread, } => { use stackify_docker_api::opts::ImageBuildOpts; let options = ImageBuildOpts::builder(path).tag(tag).build(); let images = docker.images(); #[cfg(feature = "par-compress")] { if multithread { let mut stream = images.build_par(&options); while let Some(build_result) = stream.next().await { match build_result { Ok(output) => println!("{output:?}"), Err(e) => eprintln!("Error: {e}"), } } } else { let mut stream = images.build(&options); while let Some(build_result) = stream.next().await { match build_result { Ok(output) => println!("{output:?}"), Err(e) => eprintln!("Error: {e}"), } } } } #[cfg(not(feature = "par-compress"))] { let mut stream = images.build(&options); while let Some(build_result) = stream.next().await { match build_result { Ok(output) => println!("{output:?}"), Err(e) => eprintln!("Error: {e}"), } } } } Cmd::Delete { image, force, noprune, } => { use stackify_docker_api::opts::ImageRemoveOpts; let opts = ImageRemoveOpts::builder() .force(force) .noprune(noprune) .build(); match docker.images().get(&image).remove(&opts).await { Ok(statuses) => { for status in statuses { println!("{status:?}"); } } Err(e) => eprintln!("Error: {e}"), }; } Cmd::Export { image } => { use stackify_docker_api::Error; use std::{fs::OpenOptions, io::Write}; let mut export_file = OpenOptions::new() .write(true) .create(true) .open(format!("{}.tar", &image))?; while let Some(export_result) = docker.images().get(&image).export().next().await { match export_result.and_then(|bytes| export_file.write(&bytes).map_err(Error::from)) { Ok(n) => println!("copied {n} bytes"), Err(e) => eprintln!("Error: {e}"), } } } Cmd::Inspect { image } => { match docker.images().get(&image).inspect().await { Ok(image) => println!("{image:#?}"), Err(e) => eprintln!("Error: {e}"), }; } Cmd::Import { path } => { use std::fs::File; let f = File::open(path).expect("Unable to open file"); let reader = Box::from(f); let images = docker.images(); let mut stream = images.import(reader); while let Some(import_result) = stream.next().await { match import_result { Ok(output) => println!("{output:?}"), Err(e) => eprintln!("Error: {e}"), } } } Cmd::List { all } => { use stackify_docker_api::opts::ImageListOpts; let opts = if all { ImageListOpts::builder().all(true).build() } else { Default::default() }; match docker.images().list(&opts).await { Ok(images) => { images.into_iter().for_each(|image| { println!( "---------------------------------\nCreated: {}\nId: {}\nRepo tags: {}\nLabels:\n{}", image.created, image.id, image.repo_tags.join(","), image .labels .into_iter() .map(|(k, v)| format!(" - {k}={v}")) .collect::>() .join("\n"), ); }); } Err(e) => eprintln!("Error: {e}"), } } Cmd::Pull { image, username, password, } => { use stackify_docker_api::opts::{PullOpts, RegistryAuth}; let opts = if let (Some(username), Some(pass)) = (username, password) { let auth = RegistryAuth::builder() .username(username) .password(pass) .build(); PullOpts::builder().image(image).auth(auth).build() } else { PullOpts::builder().image(image).build() }; let images = docker.images(); let mut stream = images.pull(&opts); while let Some(pull_result) = stream.next().await { match pull_result { Ok(output) => println!("{output:?}"), Err(e) => eprintln!("{e}"), } } } Cmd::Search { image } => { match docker.images().search(image).await { Ok(results) => { for result in results { println!( "{} - {}", result.name.unwrap_or_default(), result.description.unwrap_or_default() ); } } Err(e) => eprintln!("Error: {e}"), }; } Cmd::Tag { repo, image: name, tag, } => { use stackify_docker_api::api::Image; use stackify_docker_api::opts::TagOpts; let tag_opts = TagOpts::builder().repo(repo).tag(tag).build(); let image = Image::new(docker, name); if let Err(e) = image.tag(&tag_opts).await { eprintln!("Error: {e}") } } Cmd::Prune => { match docker.images().prune(&Default::default()).await { Ok(info) => println!("{info:#?}"), Err(e) => eprintln!("Error: {e}"), }; } } Ok(()) }