diff --git a/process/drivers.rs b/process/drivers.rs index 49d6167f..adc187e1 100644 --- a/process/drivers.rs +++ b/process/drivers.rs @@ -200,35 +200,30 @@ impl Driver { let _ = oci_ref; // silence lint if true { - return Ok(40); + return Ok(41); } } info!("Retrieving OS version from {oci_ref}"); - let inspect_opts = GetMetadataOpts::builder() - .image(format!( - "{}/{}", - oci_ref.resolve_registry(), - oci_ref.repository() - )) - .tag(oci_ref.tag().unwrap_or("latest")) - .platform(platform) - .build(); - - let os_version = Self::get_metadata(&inspect_opts) - .and_then(|inspection| { - inspection.get_version().ok_or_else(|| { - miette!( - "Failed to parse version from metadata for {}", - oci_ref.to_string().bold() - ) - }) + let os_version = Self::get_metadata( + &GetMetadataOpts::builder() + .image(oci_ref) + .platform(platform) + .build(), + ) + .and_then(|inspection| { + inspection.get_version().ok_or_else(|| { + miette!( + "Failed to parse version from metadata for {}", + oci_ref.to_string().bold() + ) }) - .or_else(|err| { - warn!("Unable to get version via image inspection due to error:\n{err:?}"); - get_version_run_image(oci_ref) - })?; + }) + .or_else(|err| { + warn!("Unable to get version via image inspection due to error:\n{err:?}"); + get_version_run_image(oci_ref) + })?; trace!("os_version: {os_version}"); Ok(os_version) } diff --git a/process/drivers/buildah_driver.rs b/process/drivers/buildah_driver.rs index d17dd62b..73c4b5dc 100644 --- a/process/drivers/buildah_driver.rs +++ b/process/drivers/buildah_driver.rs @@ -1,6 +1,7 @@ use std::{io::Write, process::Stdio}; use blue_build_utils::{cmd, credentials::Credentials}; +use colored::Colorize; use log::{debug, error, info, trace}; use miette::{bail, miette, IntoDiagnostic, Result}; use semver::Version; @@ -83,13 +84,20 @@ impl BuildDriver for BuildahDriver { fn tag(opts: &TagOpts) -> Result<()> { trace!("BuildahDriver::tag({opts:#?})"); - let mut command = cmd!("buildah", "tag", &*opts.src_image, &*opts.dest_image,); + let dest_image_str = opts.dest_image.to_string(); + + let mut command = cmd!( + "buildah", + "tag", + opts.src_image.to_string(), + &dest_image_str, + ); trace!("{command:?}"); if command.status().into_diagnostic()?.success() { - info!("Successfully tagged {}!", opts.dest_image); + info!("Successfully tagged {}!", dest_image_str.bold().green()); } else { - bail!("Failed to tag image {}", opts.dest_image); + bail!("Failed to tag image {}", dest_image_str.bold().red()); } Ok(()) } @@ -97,6 +105,8 @@ impl BuildDriver for BuildahDriver { fn push(opts: &PushOpts) -> Result<()> { trace!("BuildahDriver::push({opts:#?})"); + let image_str = opts.image.to_string(); + let command = cmd!( "buildah", "push", @@ -104,18 +114,18 @@ impl BuildDriver for BuildahDriver { "--compression-format={}", opts.compression_type.unwrap_or_default() ), - &*opts.image, + &image_str, ); trace!("{command:?}"); let status = command - .build_status(&opts.image, "Pushing Image") + .build_status(&image_str, "Pushing Image") .into_diagnostic()?; if status.success() { - info!("Successfully pushed {}!", opts.image); + info!("Successfully pushed {}!", image_str.bold().green()); } else { - bail!("Failed to push image {}", opts.image); + bail!("Failed to push image {}", image_str.bold().red()); } Ok(()) } diff --git a/process/drivers/cosign_driver.rs b/process/drivers/cosign_driver.rs index 0073a0d8..6b619ad0 100644 --- a/process/drivers/cosign_driver.rs +++ b/process/drivers/cosign_driver.rs @@ -5,6 +5,7 @@ use blue_build_utils::{ constants::{COSIGN_PASSWORD, COSIGN_PUB_PATH, COSIGN_YES}, credentials::Credentials, }; +use colored::Colorize; use log::{debug, trace}; use miette::{bail, miette, Context, IntoDiagnostic, Result}; @@ -121,27 +122,32 @@ impl SigningDriver for CosignDriver { } fn sign(opts: &SignOpts) -> Result<()> { - let image_digest: &str = opts.image.as_ref(); + if opts.image.digest().is_none() { + bail!( + "Image ref {} is not a digest ref", + opts.image.to_string().bold().red(), + ); + } + let mut command = cmd!( "cosign", "sign", if let Some(ref key) = opts.key => format!("--key={key}"), "--recursive", - image_digest, + opts.image.to_string(), COSIGN_PASSWORD => "", COSIGN_YES => "true", ); trace!("{command:?}"); if !command.status().into_diagnostic()?.success() { - bail!("Failed to sign {image_digest}"); + bail!("Failed to sign {}", opts.image.to_string().bold().red()); } Ok(()) } fn verify(opts: &VerifyOpts) -> Result<()> { - let image_name_tag: &str = opts.image.as_ref(); let mut command = cmd!( "cosign", "verify", @@ -157,12 +163,12 @@ impl SigningDriver for CosignDriver { ), }; }, - image_name_tag + opts.image.to_string(), ); trace!("{command:?}"); if !command.status().into_diagnostic()?.success() { - bail!("Failed to verify {image_name_tag}"); + bail!("Failed to verify {}", opts.image.to_string().bold().red()); } Ok(()) diff --git a/process/drivers/docker_driver.rs b/process/drivers/docker_driver.rs index 21d64a67..507baf0a 100644 --- a/process/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -13,6 +13,7 @@ use blue_build_utils::{ string_vec, }; use cached::proc_macro::cached; +use colored::Colorize; use log::{debug, info, trace, warn}; use miette::{bail, miette, IntoDiagnostic, Result}; use once_cell::sync::Lazy; @@ -154,15 +155,17 @@ impl BuildDriver for DockerDriver { fn tag(opts: &TagOpts) -> Result<()> { trace!("DockerDriver::tag({opts:#?})"); + let dest_image_str = opts.dest_image.to_string(); + trace!("docker tag {} {}", opts.src_image, opts.dest_image); - let status = cmd!("docker", "tag", &*opts.src_image, &*opts.dest_image,) + let status = cmd!("docker", "tag", opts.src_image.to_string(), &dest_image_str) .status() .into_diagnostic()?; if status.success() { - info!("Successfully tagged {}!", opts.dest_image); + info!("Successfully tagged {}!", dest_image_str.bold().green()); } else { - bail!("Failed to tag image {}", opts.dest_image); + bail!("Failed to tag image {}", dest_image_str.bold().red()); } Ok(()) } @@ -170,15 +173,17 @@ impl BuildDriver for DockerDriver { fn push(opts: &PushOpts) -> Result<()> { trace!("DockerDriver::push({opts:#?})"); + let image_str = opts.image.to_string(); + trace!("docker push {}", opts.image); - let status = cmd!("docker", "push", &*opts.image) + let status = cmd!("docker", "push", &image_str) .status() .into_diagnostic()?; if status.success() { - info!("Successfully pushed {}!", opts.image); + info!("Successfully pushed {}!", image_str.bold().green()); } else { - bail!("Failed to push image {}", opts.image); + bail!("Failed to push image {}", image_str.bold().red()); } Ok(()) } @@ -315,18 +320,25 @@ impl BuildDriver for DockerDriver { ], ); - let final_images = match (opts.image.as_deref(), opts.archive_path.as_deref()) { + let final_images = match (opts.image, opts.archive_path.as_deref()) { (Some(image), None) => { let images = if opts.tags.is_empty() { - cmd!(command, "-t", image); + let image = image.to_string(); + cmd!(command, "-t", &image); string_vec![image] } else { opts.tags.iter().for_each(|tag| { - cmd!(command, "-t", format!("{image}:{tag}")); + cmd!( + command, + "-t", + format!("{}/{}:{tag}", image.resolve_registry(), image.repository()) + ); }); opts.tags .iter() - .map(|tag| format!("{image}:{tag}")) + .map(|tag| { + format!("{}/{}:{tag}", image.resolve_registry(), image.repository()) + }) .collect() }; let first_image = images.first().unwrap(); @@ -348,8 +360,12 @@ impl BuildDriver for DockerDriver { images } (None, Some(archive_path)) => { - cmd!(command, "--output", format!("type=oci,dest={archive_path}")); - string_vec![archive_path] + cmd!( + command, + "--output", + format!("type=oci,dest={}", archive_path.display()) + ); + string_vec![archive_path.display().to_string()] } (Some(_), Some(_)) => bail!("Cannot use both image and archive path"), (None, None) => bail!("Need either the image or archive path set"), @@ -385,16 +401,12 @@ impl InspectDriver for DockerDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{:?}-{}", &*opts.image, opts.tag.as_ref(), opts.platform)}"#, + convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, sync_writes = true )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { trace!("DockerDriver::get_metadata({opts:#?})"); - - let url = opts.tag.as_ref().map_or_else( - || format!("{}", opts.image), - |tag| format!("{}:{tag}", opts.image), - ); + let image_str = opts.image.to_string(); let mut command = cmd!( "docker", @@ -409,16 +421,16 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { "inspect", "--format", "{{json .}}", - &url + &image_str, ); trace!("{command:?}"); let output = command.output().into_diagnostic()?; if output.status.success() { - info!("Successfully inspected image {url}!"); + info!("Successfully inspected image {}!", image_str.bold().green()); } else { - bail!("Failed to inspect image {url}") + bail!("Failed to inspect image {}", image_str.bold().red()) } serde_json::from_slice::(&output.stdout) diff --git a/process/drivers/github_driver.rs b/process/drivers/github_driver.rs index 284f7e18..6bcf8947 100644 --- a/process/drivers/github_driver.rs +++ b/process/drivers/github_driver.rs @@ -230,11 +230,11 @@ mod test { setup_default_branch, None, string_vec![ - format!("{}-40", &*TIMESTAMP), + format!("{}-41", &*TIMESTAMP), "latest", &*TIMESTAMP, - format!("{COMMIT_SHA}-40"), - "40", + format!("{COMMIT_SHA}-41"), + "41", ], )] #[case::default_branch_alt_tags( @@ -242,43 +242,43 @@ mod test { Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ TEST_TAG_1, - format!("{TEST_TAG_1}-40"), - format!("{}-{TEST_TAG_1}-40", &*TIMESTAMP), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), + format!("{TEST_TAG_1}-41"), + format!("{}-{TEST_TAG_1}-41", &*TIMESTAMP), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), TEST_TAG_2, - format!("{TEST_TAG_2}-40"), - format!("{}-{TEST_TAG_2}-40", &*TIMESTAMP), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("{TEST_TAG_2}-41"), + format!("{}-{TEST_TAG_2}-41", &*TIMESTAMP), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] #[case::pr_branch( setup_pr_branch, None, - string_vec!["pr-12-40", format!("{COMMIT_SHA}-40")], + string_vec!["pr-12-41", format!("{COMMIT_SHA}-41")], )] #[case::pr_branch_alt_tags( setup_pr_branch, Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ - format!("pr-12-{TEST_TAG_1}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), - format!("pr-12-{TEST_TAG_2}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("pr-12-{TEST_TAG_1}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), + format!("pr-12-{TEST_TAG_2}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] #[case::branch( setup_branch, None, - string_vec![format!("{COMMIT_SHA}-40"), "br-test-40"], + string_vec![format!("{COMMIT_SHA}-41"), "br-test-41"], )] #[case::branch_alt_tags( setup_branch, Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ - format!("br-{BR_REF_NAME}-{TEST_TAG_1}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), - format!("br-{BR_REF_NAME}-{TEST_TAG_2}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("br-{BR_REF_NAME}-{TEST_TAG_1}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), + format!("br-{BR_REF_NAME}-{TEST_TAG_2}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] fn generate_tags( diff --git a/process/drivers/gitlab_driver.rs b/process/drivers/gitlab_driver.rs index 26a8160a..1891fd5c 100644 --- a/process/drivers/gitlab_driver.rs +++ b/process/drivers/gitlab_driver.rs @@ -238,11 +238,11 @@ mod test { setup_default_branch, None, string_vec![ - format!("{}-40", &*TIMESTAMP), + format!("{}-41", &*TIMESTAMP), "latest", &*TIMESTAMP, - format!("{COMMIT_SHA}-40"), - "40", + format!("{COMMIT_SHA}-41"), + "41", ], )] #[case::default_branch_alt_tags( @@ -250,43 +250,43 @@ mod test { Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ TEST_TAG_1, - format!("{TEST_TAG_1}-40"), - format!("{}-{TEST_TAG_1}-40", &*TIMESTAMP), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), + format!("{TEST_TAG_1}-41"), + format!("{}-{TEST_TAG_1}-41", &*TIMESTAMP), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), TEST_TAG_2, - format!("{TEST_TAG_2}-40"), - format!("{}-{TEST_TAG_2}-40", &*TIMESTAMP), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("{TEST_TAG_2}-41"), + format!("{}-{TEST_TAG_2}-41", &*TIMESTAMP), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] #[case::pr_branch( setup_mr_branch, None, - string_vec!["mr-12-40", format!("{COMMIT_SHA}-40")], + string_vec!["mr-12-41", format!("{COMMIT_SHA}-41")], )] #[case::pr_branch_alt_tags( setup_mr_branch, Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ - format!("mr-12-{TEST_TAG_1}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), - format!("mr-12-{TEST_TAG_2}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("mr-12-{TEST_TAG_1}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), + format!("mr-12-{TEST_TAG_2}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] #[case::branch( setup_branch, None, - string_vec![format!("{COMMIT_SHA}-40"), "br-test-40"], + string_vec![format!("{COMMIT_SHA}-41"), "br-test-41"], )] #[case::branch_alt_tags( setup_branch, Some(bon::vec![TEST_TAG_1, TEST_TAG_2]), string_vec![ - format!("br-{BR_REF_NAME}-{TEST_TAG_1}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_1}-40"), - format!("br-{BR_REF_NAME}-{TEST_TAG_2}-40"), - format!("{COMMIT_SHA}-{TEST_TAG_2}-40"), + format!("br-{BR_REF_NAME}-{TEST_TAG_1}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_1}-41"), + format!("br-{BR_REF_NAME}-{TEST_TAG_2}-41"), + format!("{COMMIT_SHA}-{TEST_TAG_2}-41"), ], )] fn generate_tags( diff --git a/process/drivers/opts/build.rs b/process/drivers/opts/build.rs index c1142eed..04d097c9 100644 --- a/process/drivers/opts/build.rs +++ b/process/drivers/opts/build.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, path::Path}; use bon::Builder; +use oci_distribution::Reference; use crate::drivers::types::Platform; @@ -26,16 +27,14 @@ pub struct BuildOpts<'scope> { } #[derive(Debug, Clone, Builder)] -#[builder(on(Cow<'_, str>, into))] pub struct TagOpts<'scope> { - pub src_image: Cow<'scope, str>, - pub dest_image: Cow<'scope, str>, + pub src_image: &'scope Reference, + pub dest_image: &'scope Reference, } #[derive(Debug, Clone, Builder)] pub struct PushOpts<'scope> { - #[builder(into)] - pub image: Cow<'scope, str>, + pub image: &'scope Reference, pub compression_type: Option, } @@ -55,14 +54,13 @@ pub struct BuildTagPushOpts<'scope> { /// NOTE: This SHOULD NOT contain the tag of the image. /// /// NOTE: You cannot have this set with `archive_path` set. - #[builder(into)] - pub image: Option>, + pub image: Option<&'scope Reference>, /// The path to the archive file. /// /// NOTE: You cannot have this set with image set. #[builder(into)] - pub archive_path: Option>, + pub archive_path: Option>, /// The path to the Containerfile to build. #[builder(into)] diff --git a/process/drivers/opts/inspect.rs b/process/drivers/opts/inspect.rs index de22b735..dd2f0906 100644 --- a/process/drivers/opts/inspect.rs +++ b/process/drivers/opts/inspect.rs @@ -1,6 +1,5 @@ -use std::borrow::Cow; - use bon::Builder; +use oci_distribution::Reference; use crate::drivers::types::Platform; @@ -8,10 +7,7 @@ use crate::drivers::types::Platform; #[builder(derive(Clone))] pub struct GetMetadataOpts<'scope> { #[builder(into)] - pub image: Cow<'scope, str>, - - #[builder(into)] - pub tag: Option>, + pub image: &'scope Reference, #[builder(default)] pub platform: Platform, diff --git a/process/drivers/opts/signing.rs b/process/drivers/opts/signing.rs index e9e31715..6cfd488f 100644 --- a/process/drivers/opts/signing.rs +++ b/process/drivers/opts/signing.rs @@ -6,6 +6,7 @@ use std::{ use bon::Builder; use miette::{IntoDiagnostic, Result}; +use oci_distribution::Reference; use zeroize::{Zeroize, Zeroizing}; use crate::drivers::types::Platform; @@ -69,7 +70,7 @@ pub struct CheckKeyPairOpts<'scope> { #[derive(Debug, Clone, Builder)] pub struct SignOpts<'scope> { #[builder(into)] - pub image: Cow<'scope, str>, + pub image: &'scope Reference, #[builder(into)] pub key: Option>, @@ -90,17 +91,14 @@ pub enum VerifyType<'scope> { #[derive(Debug, Clone, Builder)] pub struct VerifyOpts<'scope> { #[builder(into)] - pub image: Cow<'scope, str>, + pub image: &'scope Reference, pub verify_type: VerifyType<'scope>, } #[derive(Debug, Clone, Builder)] pub struct SignVerifyOpts<'scope> { #[builder(into)] - pub image: Cow<'scope, str>, - - #[builder(into)] - pub tag: Option>, + pub image: &'scope Reference, #[builder(into)] pub dir: Option>, diff --git a/process/drivers/podman_driver.rs b/process/drivers/podman_driver.rs index ddba6bbb..2debe613 100644 --- a/process/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -167,15 +167,17 @@ impl BuildDriver for PodmanDriver { fn tag(opts: &TagOpts) -> Result<()> { trace!("PodmanDriver::tag({opts:#?})"); - let mut command = cmd!("podman", "tag", &*opts.src_image, &*opts.dest_image,); + let dest_image_str = opts.dest_image.to_string(); + + let mut command = cmd!("podman", "tag", opts.src_image.to_string(), &dest_image_str); trace!("{command:?}"); let status = command.status().into_diagnostic()?; if status.success() { - info!("Successfully tagged {}!", opts.dest_image); + info!("Successfully tagged {}!", dest_image_str.bold().green()); } else { - bail!("Failed to tag image {}", opts.dest_image); + bail!("Failed to tag image {}", dest_image_str.bold().red()); } Ok(()) } @@ -183,6 +185,8 @@ impl BuildDriver for PodmanDriver { fn push(opts: &PushOpts) -> Result<()> { trace!("PodmanDriver::push({opts:#?})"); + let image_str = opts.image.to_string(); + let command = cmd!( "podman", "push", @@ -190,18 +194,18 @@ impl BuildDriver for PodmanDriver { "--compression-format={}", opts.compression_type.unwrap_or_default() ), - &*opts.image, + &image_str, ); trace!("{command:?}"); let status = command - .build_status(&opts.image, "Pushing Image") + .build_status(&image_str, "Pushing Image") .into_diagnostic()?; if status.success() { - info!("Successfully pushed {}!", opts.image); + info!("Successfully pushed {}!", image_str.bold().green()); } else { - bail!("Failed to push image {}", opts.image) + bail!("Failed to push image {}", image_str.bold().red()); } Ok(()) } @@ -283,23 +287,20 @@ impl InspectDriver for PodmanDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{:?}-{}", &*opts.image, opts.tag.as_ref(), opts.platform)}"#, + convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, sync_writes = true )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { trace!("PodmanDriver::get_metadata({opts:#?})"); - let url = opts.tag.as_deref().map_or_else( - || format!("{}", opts.image), - |tag| format!("{}:{tag}", opts.image), - ); + let image_str = opts.image.to_string(); let progress = Logger::multi_progress().add( ProgressBar::new_spinner() .with_style(ProgressStyle::default_spinner()) .with_message(format!( "Inspecting metadata for {}, pulling image...", - url.bold() + image_str.bold() )), ); progress.enable_steady_tick(Duration::from_millis(100)); @@ -311,17 +312,17 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { "--platform", opts.platform.to_string(), ], - &url, + &image_str, ); trace!("{command:?}"); let output = command.output().into_diagnostic()?; if !output.status.success() { - bail!("Failed to pull {} for inspection!", url.bold()); + bail!("Failed to pull {} for inspection!", image_str.bold().red()); } - let mut command = cmd!("podman", "image", "inspect", "--format=json", &url); + let mut command = cmd!("podman", "image", "inspect", "--format=json", &image_str); trace!("{command:?}"); let output = command.output().into_diagnostic()?; @@ -330,9 +331,9 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { Logger::multi_progress().remove(&progress); if output.status.success() { - debug!("Successfully inspected image {url}!"); + debug!("Successfully inspected image {}!", image_str.bold().green()); } else { - bail!("Failed to inspect image {url}"); + bail!("Failed to inspect image {}", image_str.bold().red()); } serde_json::from_slice::>(&output.stdout) .into_diagnostic() diff --git a/process/drivers/sigstore_driver.rs b/process/drivers/sigstore_driver.rs index a5415043..dbe5d0f9 100644 --- a/process/drivers/sigstore_driver.rs +++ b/process/drivers/sigstore_driver.rs @@ -15,6 +15,7 @@ use blue_build_utils::{ credentials::Credentials, retry, }; +use colored::Colorize; use log::{debug, trace}; use miette::{bail, miette, Context, IntoDiagnostic}; use sigstore::{ @@ -107,12 +108,16 @@ impl SigningDriver for SigstoreDriver { fn sign(opts: &SignOpts) -> miette::Result<()> { trace!("SigstoreDriver::sign({opts:?})"); + if opts.image.digest().is_none() { + bail!( + "Image ref {} is not a digest ref", + opts.image.to_string().bold().red(), + ); + } + let path = opts.dir.as_ref().map_or_else(|| Path::new("."), |dir| dir); let mut client = ClientBuilder::default().build().into_diagnostic()?; - - let image_digest: &str = opts.image.as_ref(); - let image_digest: OciReference = image_digest.parse().into_diagnostic()?; - trace!("{image_digest:?}"); + let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?; let signing_scheme = SigningScheme::default(); let key: Zeroizing> = get_private_key(path)?.contents()?; @@ -174,8 +179,7 @@ impl SigningDriver for SigstoreDriver { fn verify(opts: &VerifyOpts) -> miette::Result<()> { let mut client = ClientBuilder::default().build().into_diagnostic()?; - let image_digest: &str = opts.image.as_ref(); - let image_digest: OciReference = image_digest.parse().into_diagnostic()?; + let image_digest: OciReference = opts.image.to_string().parse().into_diagnostic()?; trace!("{image_digest:?}"); let signing_scheme = SigningScheme::default(); diff --git a/process/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs index 747b2b90..881eae86 100644 --- a/process/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -23,21 +23,18 @@ impl InspectDriver for SkopeoDriver { #[cached( result = true, key = "String", - convert = r#"{ format!("{}-{:?}-{}", &*opts.image, opts.tag.as_ref(), opts.platform)}"#, + convert = r#"{ format!("{}-{}", opts.image, opts.platform)}"#, sync_writes = true )] fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { trace!("SkopeoDriver::get_metadata({opts:#?})"); - let url = opts.tag.as_ref().map_or_else( - || format!("docker://{}", opts.image), - |tag| format!("docker://{}:{tag}", opts.image), - ); + let image_str = opts.image.to_string(); let progress = Logger::multi_progress().add( ProgressBar::new_spinner() .with_style(ProgressStyle::default_spinner()) - .with_message(format!("Inspecting metadata for {}", url.bold())), + .with_message(format!("Inspecting metadata for {}", image_str.bold())), ); progress.enable_steady_tick(Duration::from_millis(100)); @@ -48,7 +45,7 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { opts.platform.arch(), ], "inspect", - &url, + format!("docker://{image_str}"), stderr = Stdio::inherit(), ); trace!("{command:?}"); @@ -59,9 +56,9 @@ fn get_metadata_cache(opts: &GetMetadataOpts) -> Result { Logger::multi_progress().remove(&progress); if output.status.success() { - debug!("Successfully inspected image {url}!"); + debug!("Successfully inspected image {}!", image_str.bold().green()); } else { - bail!("Failed to inspect image {url}") + bail!("Failed to inspect image {}", image_str.bold().red()); } serde_json::from_slice(&output.stdout).into_diagnostic() } diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs index 4eaaa89c..4c10bb85 100644 --- a/process/drivers/traits.rs +++ b/process/drivers/traits.rs @@ -6,7 +6,7 @@ use std::{ use blue_build_utils::{constants::COSIGN_PUB_PATH, retry, string_vec}; use log::{debug, info, trace}; -use miette::{bail, miette, Context, IntoDiagnostic, Result}; +use miette::{bail, Context, IntoDiagnostic, Result}; use oci_distribution::Reference; use semver::{Version, VersionReq}; @@ -127,12 +127,9 @@ pub trait BuildDriver: PrivateDriver { let full_image = match (opts.archive_path.as_ref(), opts.image.as_ref()) { (Some(archive_path), None) => { - format!("oci-archive:{archive_path}") + format!("oci-archive:{}", archive_path.display()) } - (None, Some(image)) => opts - .tags - .first() - .map_or_else(|| image.to_string(), |tag| format!("{image}:{tag}")), + (None, Some(image)) => image.to_string(), (Some(_), Some(_)) => bail!("Cannot use both image and archive path"), (None, None) => bail!("Need either the image or archive path set"), }; @@ -148,25 +145,22 @@ pub trait BuildDriver: PrivateDriver { Self::build(&build_opts)?; let image_list: Vec = if !opts.tags.is_empty() && opts.archive_path.is_none() { - let image = opts - .image - .as_ref() - .ok_or_else(|| miette!("Image is required in order to tag"))?; + let image = opts.image.unwrap(); debug!("Tagging all images"); let mut image_list = Vec::with_capacity(opts.tags.len()); for tag in &opts.tags { debug!("Tagging {} with {tag}", &full_image); - let tagged_image = format!("{image}:{tag}"); + let tagged_image: Reference = format!("{image}:{tag}").parse().into_diagnostic()?; let tag_opts = TagOpts::builder() - .src_image(&full_image) + .src_image(image) .dest_image(&tagged_image) .build(); Self::tag(&tag_opts)?; - image_list.push(tagged_image); + image_list.push(tagged_image.to_string()); if opts.push { let retry_count = if opts.retry_push { opts.retry_count } else { 0 }; @@ -174,7 +168,8 @@ pub trait BuildDriver: PrivateDriver { debug!("Pushing all images"); // Push images with retries (1s delay between retries) blue_build_utils::retry(retry_count, 5, || { - let tag_image = format!("{image}:{tag}"); + let tag_image: Reference = + format!("{image}:{tag}").parse().into_diagnostic()?; debug!("Pushing image {tag_image}"); @@ -516,23 +511,20 @@ pub trait SigningDriver: PrivateDriver { .as_ref() .map_or_else(|| PathBuf::from("."), |d| d.to_path_buf()); - let image_name: &str = opts.image.as_ref(); - let inspect_opts = GetMetadataOpts::builder() - .image(image_name) - .platform(opts.platform); - - let inspect_opts = if let Some(ref tag) = opts.tag { - inspect_opts.tag(&**tag).build() - } else { - inspect_opts.build() - }; - - let image_digest = Driver::get_metadata(&inspect_opts)?.digest; - let image_name_tag = opts - .tag - .as_ref() - .map_or_else(|| image_name.to_owned(), |t| format!("{image_name}:{t}")); - let image_digest = format!("{image_name}@{image_digest}"); + let image_digest = Driver::get_metadata( + &GetMetadataOpts::builder() + .image(opts.image) + .platform(opts.platform) + .build(), + )? + .digest; + let image_digest: Reference = format!( + "{}/{}@{image_digest}", + opts.image.resolve_registry(), + opts.image.repository(), + ) + .parse() + .into_diagnostic()?; let (sign_opts, verify_opts) = match (Driver::get_ci_driver(), get_private_key(&path)) { // Cosign public/private key pair @@ -543,7 +535,7 @@ pub trait SigningDriver: PrivateDriver { .key(priv_key.to_string()) .build(), VerifyOpts::builder() - .image(&image_name_tag) + .image(opts.image) .verify_type(VerifyType::File(path.join(COSIGN_PUB_PATH).into())) .build(), ), @@ -551,7 +543,7 @@ pub trait SigningDriver: PrivateDriver { (CiDriverType::Github | CiDriverType::Gitlab, _) => ( SignOpts::builder().dir(&path).image(&image_digest).build(), VerifyOpts::builder() - .image(&image_name_tag) + .image(opts.image) .verify_type(VerifyType::Keyless { issuer: Driver::oidc_provider()?.into(), identity: Driver::keyless_cert_identity()?.into(), diff --git a/recipe/src/module.rs b/recipe/src/module.rs index e9701577..7093ea67 100644 --- a/recipe/src/module.rs +++ b/recipe/src/module.rs @@ -11,11 +11,15 @@ use serde_yaml::Value; use crate::{base_recipe_path, AkmodsInfo, ModuleExt}; -#[derive(Serialize, Deserialize, Debug, Clone, Builder, Default)] +mod type_ver; + +pub use type_ver::*; + +#[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct ModuleRequiredFields<'a> { #[builder(into)] #[serde(rename = "type")] - pub module_type: Cow<'a, str>, + pub module_type: ModuleTypeVersion<'a>, #[builder(into)] #[serde(skip_serializing_if = "Option::is_none")] @@ -38,7 +42,7 @@ const fn is_false(b: &bool) -> bool { impl<'a> ModuleRequiredFields<'a> { #[must_use] pub fn get_module_type_list(&'a self, typ: &str, list_key: &str) -> Option> { - if self.module_type == typ { + if self.module_type.typ() == typ { Some( self.config .get(list_key)? @@ -217,7 +221,7 @@ impl Module<'_> { required_fields: None, from_file: Some(file_name), } => { - let file_name = PathBuf::from(file_name.as_ref()); + let file_name = PathBuf::from(&**file_name); if traversed_files.contains(&file_name) { bail!( "{} File {} has already been parsed:\n{traversed_files:?}", @@ -260,14 +264,21 @@ impl Module<'_> { } #[must_use] + #[allow(clippy::missing_panics_doc)] pub fn example() -> Self { Self::builder() .required_fields( ModuleRequiredFields::builder() - .module_type("module-name") + .module_type("script") .config(IndexMap::from_iter([ - ("module".to_string(), Value::String("config".to_string())), - ("goes".to_string(), Value::String("here".to_string())), + ( + "snippets".to_string(), + Value::Sequence(bon::vec!["echo 'Hello World!'"]), + ), + ( + "scripts".to_string(), + Value::Sequence(bon::vec!["install-program.sh"]), + ), ])) .build(), ) diff --git a/recipe/src/module/type_ver.rs b/recipe/src/module/type_ver.rs new file mode 100644 index 00000000..4de2d6e3 --- /dev/null +++ b/recipe/src/module/type_ver.rs @@ -0,0 +1,78 @@ +use std::borrow::Cow; + +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Debug, Clone)] +pub struct ModuleTypeVersion<'scope> { + typ: Cow<'scope, str>, + version: Cow<'scope, str>, +} + +impl<'scope> ModuleTypeVersion<'scope> { + #[must_use] + pub fn typ(&self) -> &str { + &self.typ + } + + #[must_use] + pub fn version(&self) -> &str { + &self.version + } +} + +impl std::fmt::Display for ModuleTypeVersion<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}@{}", &self.typ, &self.version) + } +} + +impl<'scope> From<&'scope str> for ModuleTypeVersion<'scope> { + fn from(s: &'scope str) -> Self { + if let Some((typ, version)) = s.split_once('@') { + Self { + typ: Cow::Borrowed(typ), + version: Cow::Borrowed(version), + } + } else { + Self { + typ: Cow::Borrowed(s), + version: Cow::Owned("latest".into()), + } + } + } +} + +impl From for ModuleTypeVersion<'_> { + fn from(s: String) -> Self { + if let Some((typ, version)) = s.split_once('@') { + Self { + typ: Cow::Owned(typ.to_owned()), + version: Cow::Owned(version.to_owned()), + } + } else { + Self { + typ: Cow::Owned(s), + version: Cow::Owned("latest".into()), + } + } + } +} + +impl Serialize for ModuleTypeVersion<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for ModuleTypeVersion<'_> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value: String = Deserialize::deserialize(deserializer)?; + Ok(value.into()) + } +} diff --git a/recipe/src/module_ext.rs b/recipe/src/module_ext.rs index 5e871345..193355b6 100644 --- a/recipe/src/module_ext.rs +++ b/recipe/src/module_ext.rs @@ -72,7 +72,7 @@ impl ModuleExt<'_> { module .required_fields .as_ref() - .is_some_and(|rf| rf.module_type == "akmods") + .is_some_and(|rf| rf.module_type.typ() == "akmods") }) .filter_map(|module| { Some( diff --git a/recipe/src/stage.rs b/recipe/src/stage.rs index 0b4bb6b4..4e5d77ef 100644 --- a/recipe/src/stage.rs +++ b/recipe/src/stage.rs @@ -108,7 +108,7 @@ impl Stage<'_> { required_fields: None, from_file: Some(file_name), } => { - let file_name = PathBuf::from(file_name.as_ref()); + let file_name = PathBuf::from(&**file_name); if traversed_files.contains(&file_name) { bail!( "{} File {} has already been parsed:\n{traversed_files:?}", diff --git a/src/commands/build.rs b/src/commands/build.rs index e143578c..abaf39e9 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -26,6 +26,7 @@ use bon::Builder; use clap::Args; use log::{info, trace, warn}; use miette::{bail, IntoDiagnostic, Result}; +use oci_distribution::Reference; use tempfile::TempDir; use crate::commands::generate::GenerateCommand; @@ -299,12 +300,15 @@ impl BuildCommand { .build(), )?; let image_name = self.image_name(&recipe)?; + let image: Reference = format!("{image_name}:{}", tags.first().map_or("latest", |tag| tag)) + .parse() + .into_diagnostic()?; let build_fn = || -> Result> { Driver::build_tag_push(&self.archive.as_ref().map_or_else( || { BuildTagPushOpts::builder() - .image(&image_name) + .image(&image) .containerfile(containerfile) .platform(self.platform) .tags(tags.collect_cow_vec()) @@ -319,11 +323,11 @@ impl BuildCommand { BuildTagPushOpts::builder() .containerfile(containerfile) .platform(self.platform) - .archive_path(format!( + .archive_path(PathBuf::from(format!( "{}/{}.{ARCHIVE_SUFFIX}", archive_dir.to_string_lossy().trim_end_matches('/'), recipe.name.to_lowercase().replace('/', "_"), - )) + ))) .squash(self.squash) .build() }, @@ -337,6 +341,10 @@ impl BuildCommand { InspectDriver, RechunkDriver, }; + let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version) + .parse() + .into_diagnostic()?; + Driver::rechunk( &RechunkOpts::builder() .image(&image_name) @@ -357,8 +365,7 @@ impl BuildCommand { .base_digest( Driver::get_metadata( &GetMetadataOpts::builder() - .image(&*recipe.base_image) - .tag(&*recipe.image_version) + .image(&base_image) .platform(self.platform) .build(), )? @@ -382,10 +389,9 @@ impl BuildCommand { if self.push && !self.no_sign { Driver::sign_and_verify( &SignVerifyOpts::builder() - .image(&image_name) + .image(&image) .retry_push(self.retry_push) .retry_count(self.retry_count) - .maybe_tag(tags.first()) .platform(self.platform) .build(), )?; diff --git a/src/commands/generate.rs b/src/commands/generate.rs index 246cf7b4..6b05d8ac 100644 --- a/src/commands/generate.rs +++ b/src/commands/generate.rs @@ -17,6 +17,7 @@ use cached::proc_macro::cached; use clap::{crate_version, Args}; use log::{debug, info, trace, warn}; use miette::{IntoDiagnostic, Result}; +use oci_distribution::Reference; #[cfg(feature = "validate")] use crate::commands::validate::ValidateCommand; @@ -132,6 +133,10 @@ impl GenerateCommand { info!("Templating for recipe at {}", recipe_path.display()); + let base_image: Reference = format!("{}:{}", &recipe.base_image, &recipe.image_version) + .parse() + .into_diagnostic()?; + let template = ContainerFileTemplate::builder() .os_version( Driver::get_os_version() @@ -144,12 +149,11 @@ impl GenerateCommand { .recipe_path(recipe_path.as_path()) .registry(registry) .repo(Driver::get_repo_url()?) - .build_scripts_image(determine_scripts_tag(self.platform)?) + .build_scripts_image(determine_scripts_tag(self.platform)?.to_string()) .base_digest( Driver::get_metadata( &GetMetadataOpts::builder() - .image(&*recipe.base_image) - .tag(&*recipe.image_version) + .image(&base_image) .platform(self.platform) .build(), )? @@ -178,24 +182,30 @@ impl GenerateCommand { convert = r#"{ platform }"#, sync_writes = true )] -fn determine_scripts_tag(platform: Platform) -> Result { - let version = format!("v{}", crate_version!()); - let opts = GetMetadataOpts::builder() - .image(BUILD_SCRIPTS_IMAGE_REF) - .platform(platform); +fn determine_scripts_tag(platform: Platform) -> Result { + let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::COMMIT_HASH) + .parse() + .into_diagnostic()?; + let opts = GetMetadataOpts::builder().platform(platform); - Driver::get_metadata(&opts.clone().tag(shadow::COMMIT_HASH).build()) + Driver::get_metadata(&opts.clone().image(&image).build()) .inspect_err(|e| trace!("{e:?}")) - .map(|_| format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::COMMIT_HASH)) + .map(|_| image) .or_else(|_| { - Driver::get_metadata(&opts.clone().tag(shadow::BRANCH).build()) + let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::BRANCH) + .parse() + .into_diagnostic()?; + Driver::get_metadata(&opts.clone().image(&image).build()) .inspect_err(|e| trace!("{e:?}")) - .map(|_| format!("{BUILD_SCRIPTS_IMAGE_REF}:{}", shadow::BRANCH)) + .map(|_| image) }) .or_else(|_| { - Driver::get_metadata(&opts.tag(&version).build()) + let image: Reference = format!("{BUILD_SCRIPTS_IMAGE_REF}:v{}", crate_version!()) + .parse() + .into_diagnostic()?; + Driver::get_metadata(&opts.image(&image).build()) .inspect_err(|e| trace!("{e:?}")) - .map(|_| format!("{BUILD_SCRIPTS_IMAGE_REF}:{version}")) + .map(|_| image) }) .inspect(|image| debug!("Using build scripts image: {image}")) } diff --git a/template/templates/modules/modules.j2 b/template/templates/modules/modules.j2 index 08630320..443e5aa6 100644 --- a/template/templates/modules/modules.j2 +++ b/template/templates/modules/modules.j2 @@ -6,9 +6,9 @@ ARG CACHEBUST="{{ build_id }}" {%- endif %} - {%- if module.module_type == "containerfile" %} + {%- if module.module_type.typ() == "containerfile" %} {%- include "modules/containerfile/containerfile.j2" %} - {%- else if module.module_type == "copy" %} + {%- else if module.module_type.typ() == "copy" %} {%- include "modules/copy/copy.j2" %} {%- else %} RUN \ @@ -22,15 +22,15 @@ RUN \ {%- else if module.is_local_source() %} --mount=type=bind,from=stage-modules,src=/modules,dst=/tmp/modules,rw \ {%- else %} - --mount=type=bind,from=ghcr.io/blue-build/modules/{{ module.module_type }}:latest,src=/modules,dst=/tmp/modules,rw \ + --mount=type=bind,from={{ blue_build_utils::constants::BLUE_BUILD_MODULE_IMAGE_REF }}/{{ module.module_type.typ() }}:{{ module.module_type.version() }},src=/modules,dst=/tmp/modules,rw \ {%- endif %} - {%- if module.module_type == "akmods" %} + {%- if module.module_type.typ() == "akmods" %} --mount=type=bind,from=stage-akmods-{{ module.generate_akmods_info(os_version).stage_name }},src=/rpms,dst=/tmp/rpms,rw \ {%- endif %} --mount=type=bind,from={{ build_scripts_image }},src=/scripts/,dst=/tmp/scripts/ \ --mount=type=cache,dst=/var/cache/rpm-ostree,id=rpm-ostree-cache-{{ recipe.name }}-{{ recipe.image_version }},sharing=locked \ --mount=type=cache,dst=/var/cache/libdnf5,id=dnf-cache-{{ recipe.name }}-{{ recipe.image_version }},sharing=locked \ - /tmp/scripts/run_module.sh '{{ module.module_type }}' '{{ module|json|safe }}' + /tmp/scripts/run_module.sh '{{ module.module_type.typ() }}' '{{ module|json|safe }}' {%- endif %} {%- endif %} {%- endfor %} @@ -45,9 +45,9 @@ RUN \ ARG CACHEBUST="{{ build_id }}" {%- endif %} - {%- if module.module_type == "containerfile" %} + {%- if module.module_type.typ() == "containerfile" %} {%- include "modules/containerfile/containerfile.j2" %} - {%- else if module.module_type == "copy" %} + {%- else if module.module_type.typ() == "copy" %} {%- include "modules/copy/copy.j2" %} {%- else %} RUN \ @@ -58,11 +58,13 @@ RUN \ {%- endif %} {%- if let Some(source) = module.get_non_local_source() %} --mount=type=bind,from={{ source }},src=/modules,dst=/tmp/modules,rw \ - {%- else %} + {%- else if module.is_local_source() %} --mount=type=bind,from=stage-modules,src=/modules,dst=/tmp/modules,rw \ + {%- else %} + --mount=type=bind,from={{ blue_build_utils::constants::BLUE_BUILD_MODULE_IMAGE_REF }}/{{ module.module_type.typ() }}:{{ module.module_type.version() }},src=/modules,dst=/tmp/modules,rw \ {%- endif %} --mount=type=bind,from={{ build_scripts_image }},src=/scripts/,dst=/tmp/scripts/ \ - /tmp/scripts/run_module.sh '{{ module.module_type }}' '{{ module|json|safe }}' + /tmp/scripts/run_module.sh '{{ module.module_type.typ() }}' '{{ module|json|safe }}' {%- endif %} {%- endif %} {%- endfor %} diff --git a/utils/src/constants.rs b/utils/src/constants.rs index 80d5790d..23498554 100644 --- a/utils/src/constants.rs +++ b/utils/src/constants.rs @@ -75,6 +75,7 @@ pub const XDG_RUNTIME_DIR: &str = "XDG_RUNTIME_DIR"; // Misc pub const BUILD_SCRIPTS_IMAGE_REF: &str = "ghcr.io/blue-build/cli/build-scripts"; pub const BLUE_BULID_IMAGE_REF: &str = "ghcr.io/blue-build/cli"; +pub const BLUE_BUILD_MODULE_IMAGE_REF: &str = "ghcr.io/blue-build/modules"; pub const COSIGN_IMAGE: &str = "ghcr.io/sigstore/cosign/cosign:v2.4.1"; pub const NUSHELL_IMAGE: &str = "ghcr.io/blue-build/nushell-image:latest"; pub const OCI_ARCHIVE: &str = "oci-archive";