From 38fe0ff4fbf89922cec69eeeb0cc0e48449f9940 Mon Sep 17 00:00:00 2001 From: STEVAN Antoine <antoine.stevan@isae-supaero.fr> Date: Thu, 10 Apr 2025 11:20:59 +0000 Subject: [PATCH] add `container list` for Docker containers (dragoon/komodo!207) ## examples ```bash ./make.rs container list --json | from ndjson | into datetime CreatedAt | into filesize Size VirtualSize | reject CreatedSince ``` or ```bash ./make.rs container list --json | from ndjson | into datetime CreatedAt | into filesize Size VirtualSize | reject CreatedSince | select ID Repository Tag CreatedAt VirtualSize | update Tag { str substring 0..<7 } ``` ## changelog - transform options of `container` to sub-subcommands - `container` --> `container build` - `container --login` --> `container login` - `container --push` --> `container push` - add `container list` to print the local images for the GitLab and GitHub repositories - `container list` will print in a pretty table - `container list --json` will print as NDJSON, i.e. one image per line as JSON - use wrappers around `nob::run_cmd_as_vec_and_fail!` - `extend_and_run` to run a partial command with an extra vector of args - `extend_and_run_and_capture_silent` to run a partial command with an extra vector of args and silently capture the stdout into a string - this allows to get rid of the `#[rustfmt::skip]` _directive_ on `main` and format the code not too aggressively --- make.rs | 158 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 42 deletions(-) diff --git a/make.rs b/make.rs index 4a3cf762..ec5dba90 100755 --- a/make.rs +++ b/make.rs @@ -8,10 +8,16 @@ //! [dependencies] //! nob = { git = "https://gitlab.isae-supaero.fr/a.stevan/nob.rs", rev = "7ea6be855cf5600558440def6e59a83f78b8b543" } //! clap = { version = "4.5.17", features = ["derive"] } +//! +//! # for `container --list` +//! serde = { version = "1.0", features = ["derive"] } +//! serde_json = "1.0" +//! prettytable = "0.10.0" //! ``` -extern crate clap; use clap::{Parser, Subcommand}; +use prettytable::{format, Cell, Row, Table}; +use serde_json::Value; const REGISTRY: &str = "gitlab-registry.isae-supaero.fr"; const MIRROR_REGISTRY: &str = "ghcr.io/dragoon-rs"; @@ -61,17 +67,26 @@ enum Commands { features: bool, }, /// Builds the container. - Container { - /// Log into the registry instead of building. - #[arg(short, long)] - login: bool, - /// Push to the registry instead of building. - #[arg(short, long)] - push: bool, + #[command(subcommand)] + Container(ContainerCommands), +} + +#[derive(Subcommand)] +enum ContainerCommands { + /// Build the current dockerfile. + Build, + /// List the local images. + List { + /// Print the output table as NDJSON instead of pretty table. + #[arg(long)] + json: bool, }, + /// Log into the registry instead of building. + Login, + /// Push to the registry instead of building. + Push, } -#[rustfmt::skip] fn main() { let cli = Cli::parse(); @@ -84,10 +99,11 @@ fn main() { } } Some(Commands::Check) => { - nob::run_cmd_and_fail!("cargo", "check", "--workspace", "--all-targets"); - nob::run_cmd_and_fail!("cargo", "check", "--workspace", "--all-targets", "--features", "kzg"); - nob::run_cmd_and_fail!("cargo", "check", "--workspace", "--all-targets", "--features", "aplonk"); - nob::run_cmd_and_fail!("cargo", "check", "--workspace", "--all-targets", "--all-features"); + let cmd = vec!["cargo", "check", "--workspace", "--all-targets"]; + extend_and_run(&cmd, &[]); + extend_and_run(&cmd, &["--features", "kzg"]); + extend_and_run(&cmd, &["--features", "aplonk"]); + extend_and_run(&cmd, &["--all-features"]); } Some(Commands::Clippy) => { nob::run_cmd_and_fail!( @@ -104,7 +120,9 @@ fn main() { Some(Commands::Test { verbose, examples }) => { let mut cmd = vec!["cargo", "test"]; - if *verbose { cmd.push("--verbose") } + if *verbose { + cmd.push("--verbose") + } if *examples { cmd.push("--examples"); } else { @@ -127,40 +145,96 @@ fn main() { features, }) => { let mut cmd = vec!["cargo", "doc", "--no-deps"]; - if *open { cmd.push("--open") } - if *private { cmd.push("--document-private-items") } - if *features { cmd.push("--all-features") } + if *open { + cmd.push("--open") + } + if *private { + cmd.push("--document-private-items") + } + if *features { + cmd.push("--all-features") + } nob::run_cmd_as_vec_and_fail!(cmd ; "RUSTDOCFLAGS" => "--html-in-header katex.html"); } - Some(Commands::Container { login, push }) => { + Some(Commands::Container(container_cmd)) => { let res = nob::run_cmd_and_fail!(@+"git", "rev-parse", "HEAD"); let sha = String::from_utf8(res.stdout).expect("Invalid UTF-8 string"); - let image = format!("{}/{}:{}", REGISTRY, IMAGE, sha.trim()); - let mirror_image = format!("{}/{}:{}", MIRROR_REGISTRY, IMAGE, sha.trim()); - - if *login { - nob::run_cmd_and_fail!("docker", "login", REGISTRY); - nob::run_cmd_and_fail!("docker", "login", MIRROR_REGISTRY); - } else if *push { - nob::run_cmd_and_fail!("docker", "push", &image); - nob::run_cmd_and_fail!("docker", "push", &mirror_image); - } else { - nob::run_cmd_and_fail!( - "docker", - "build", - "-t", &image, - ".", - "--file", DOCKERFILE - ); - nob::run_cmd_and_fail!( - "docker", - "build", - "-t", &mirror_image, - ".", - "--file", DOCKERFILE - ); + + let repo = format!("{}/{}", REGISTRY, IMAGE); + let image = format!("{}:{}", repo, sha.trim()); + + let mirror_repo = format!("{}/{}", MIRROR_REGISTRY, IMAGE); + let mirror_image = format!("{}:{}", mirror_repo, sha.trim()); + + match container_cmd { + ContainerCommands::Login => { + nob::run_cmd_and_fail!("docker", "login", REGISTRY); + nob::run_cmd_and_fail!("docker", "login", MIRROR_REGISTRY); + } + ContainerCommands::Build => { + let cmd = vec!["docker", "build", ".", "--file", DOCKERFILE]; + extend_and_run(&cmd, &["-t", &image]); + extend_and_run(&cmd, &["-t", &mirror_image]); + } + ContainerCommands::List { json } => { + let cmd = vec!["docker", "image", "list", "--format", "json"]; + let images = extend_and_run_and_capture_silent(&cmd, &[&repo]) + + &extend_and_run_and_capture_silent(&cmd, &[&mirror_repo]); + + if *json { + println!("{}", images); + } else { + docker_images_to_table(images).printstd(); + } + } + ContainerCommands::Push => { + nob::run_cmd_and_fail!("docker", "push", &image); + nob::run_cmd_and_fail!("docker", "push", &mirror_image); + } } } None => {} } } + +fn docker_images_to_table(lines: String) -> Table { + let mut rows: Vec<Vec<String>> = Vec::new(); + let mut headers: Vec<String> = Vec::new(); + for line in lines.lines() { + let json: Value = serde_json::from_str(&line).unwrap_or_else(|_| Value::Null); + if let Value::Object(map) = serde_json::from_str(&line).unwrap_or_else(|_| Value::Null) { + if headers.is_empty() { + headers = map.keys().cloned().collect(); + } + + let row: Vec<String> = headers + .iter() + .map(|key| map.get(key).map_or("".to_string(), |v| v.to_string())) + .collect(); + rows.push(row); + } + } + + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(Row::new(headers.iter().map(|h| Cell::new(h)).collect())); + for row in rows { + table.add_row(Row::new(row.iter().map(|v| Cell::new(v)).collect())); + } + + table +} + +// NOTE: this could be migrated to [`nob.rs`](https://gitlab.isae-supaero.fr/a.stevan/nob.rs) +fn extend_and_run(cmd: &[&str], args: &[&str]) { + let mut cmd = cmd.to_vec(); + cmd.extend_from_slice(&args); + nob::run_cmd_as_vec_and_fail!(cmd); +} + +// NOTE: this could be migrated to [`nob.rs`](https://gitlab.isae-supaero.fr/a.stevan/nob.rs) +fn extend_and_run_and_capture_silent(cmd: &[&str], args: &[&str]) -> String { + let mut cmd = cmd.to_vec(); + cmd.extend_from_slice(&args); + String::from_utf8(nob::run_cmd_as_vec_and_fail!(@+cmd).stdout).expect("Invalid UTF-8 string") +} -- GitLab