From 5ef9a9d4fd518c4442ed43145a84aa0ef1ef6c8f Mon Sep 17 00:00:00 2001 From: Robert Detjens Date: Thu, 17 Oct 2024 23:41:47 -0700 Subject: [PATCH] implement image pushing and --push/--no-push for build command Signed-off-by: Robert Detjens --- src/builder/docker.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- src/builder/mod.rs | 20 +++++++++++++++----- src/cli.rs | 11 +++++++---- src/commands/build.rs | 14 +++++++++++++- src/main.rs | 9 +++++++-- tests/repo/rcds.yaml | 2 +- 6 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/builder/docker.rs b/src/builder/docker.rs index 4c43227..0539760 100644 --- a/src/builder/docker.rs +++ b/src/builder/docker.rs @@ -71,7 +71,48 @@ pub async fn build_image(context: &Path, options: &BuildObject, tag: &str) -> Re } } - Ok("".to_string()) + Ok(tag.to_string()) +} + +#[tokio::main(flavor = "current_thread")] // make this a sync function +pub async fn push_image(image_tag: &str, creds: &UserPass) -> Result { + info!("pushing image {image_tag:?} to registry"); + let client = client() + .await + // truncate error chain with new error (returned error is way too verbose) + .map_err(|_| anyhow!("could not talk to Docker daemon (is DOCKER_HOST correct?)"))?; + + let (image, tag) = image_tag + .rsplit_once(":") + .context("failed to get tag from full image string")?; + + let opts = PushImageOptions { tag }; + let creds = DockerCredentials { + username: Some(creds.user.clone()), + password: Some(creds.pass.clone()), + ..Default::default() + }; + + let mut push_stream = client.push_image(image, Some(opts), Some(creds)); + + // stream output to stdout + while let Some(item) = push_stream.next().await { + match item { + // error from stream? + Err(DockerError::DockerResponseServerError { + status_code, + message, + }) => bail!("error from daemon: {message}"), + Err(e) => bail!("{e:?}"), + Ok(msg) => { + debug!("{msg:?}"); + if let Some(progress) = msg.progress_detail { + info!("progress: {:?}/{:?}", progress.current, progress.total); + } + } + } + } + Ok(tag.to_string()) } // diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 6a6c82b..c47701a 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -48,8 +48,7 @@ fn build_challenge_images(profile_name: &str, chal: &ChallengeConfig) -> Result< debug!("building images for chal {:?}", chal.directory); let config = get_config()?; - let built_tags = chal - .pods + chal.pods .iter() .filter_map(|p| match &p.image_source { Image(_) => None, @@ -72,9 +71,20 @@ fn build_challenge_images(profile_name: &str, chal: &ChallengeConfig) -> Result< ) } }) - .collect::>()?; + .collect::>() +} + +/// Push passed tags to registry +pub fn push_tags(tags: Vec) -> Result> { + let config = get_config()?; - trace!("built these images: {built_tags:?}"); + let built_tags = tags + .iter() + .map(|tag| { + push_image(tag, &config.registry.build) + .with_context(|| format!("error pushing image {tag}")) + }) + .collect::>()?; - return Ok(built_tags); + Ok(built_tags) } diff --git a/src/cli.rs b/src/cli.rs index c15a37f..a232e13 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -20,12 +20,15 @@ pub enum Commands { /// Deployment profile #[arg(short, long, value_name = "PROFILE")] profile: String, - - /// Whether to push container images to registry (default: true) + /// Push container images to registry (default: true) #[arg(long, default_value = "true")] - // TODO: no way to actually set False... - // maybe revisit when negation flags are implemented: https://github.com/clap-rs/clap/issues/815 push: bool, + + /// Don't push container images to registry + #[arg(long, default_value = "false")] + no_push: bool, + // TODO: this is hacky. revisit when automatic negation flags are implemented: + // https://github.com/clap-rs/clap/issues/815 }, /// Deploy enabled challenges to cluster, updating any backing resources as necessary. diff --git a/src/commands/build.rs b/src/commands/build.rs index 3c86093..e4c1852 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -1,7 +1,7 @@ use simplelog::*; use std::process::exit; -use crate::builder::build_challenges; +use crate::builder::{build_challenges, push_tags}; use crate::configparser::{get_config, get_profile_config}; pub fn run(profile_name: &str, push: &bool) { @@ -15,4 +15,16 @@ pub fn run(profile_name: &str, push: &bool) { } }; info!("images built successfully!"); + + if *push { + info!("pushing images..."); + + match push_tags(tags) { + Ok(_) => info!("images pushed successfully!"), + Err(e) => { + error!("{e:?}"); + exit(1) + } + } + }; } diff --git a/src/main.rs b/src/main.rs index 7bf7383..ab1910f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,9 +35,14 @@ fn main() { commands::check_access::run(profile, kubernetes, frontend, registry) } - cli::Commands::Build { profile, push } => { + #[allow(unused_variables)] + cli::Commands::Build { + profile, + push, + no_push, + } => { commands::validate::run(); - commands::build::run(profile, push) + commands::build::run(profile, &!no_push) } cli::Commands::Deploy { diff --git a/tests/repo/rcds.yaml b/tests/repo/rcds.yaml index 8369ca6..ff035c9 100644 --- a/tests/repo/rcds.yaml +++ b/tests/repo/rcds.yaml @@ -1,7 +1,7 @@ flag_regex: dam{[a-zA-Z...]} registry: - domain: registry.localhost:5000/damctf + domain: localhost:5000/damctf # then environment variables e.g. REG_USER/REG_PASS build: user: admin