From 4e0ab374c9691a7aab80a0c439af081a599c99b2 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Thu, 3 Oct 2024 17:50:59 -0400 Subject: [PATCH] fix: Use built-in image inspection for podman and docker --- process/drivers.rs | 6 +- process/drivers/docker_driver.rs | 114 +++++++++++++++++++++++------- process/drivers/image_metadata.rs | 26 ------- process/drivers/podman_driver.rs | 96 +++++++++++++++++++------ process/drivers/skopeo_driver.rs | 2 +- process/drivers/traits.rs | 2 +- process/drivers/types.rs | 26 ++++++- src/commands/build.rs | 1 + 8 files changed, 194 insertions(+), 79 deletions(-) delete mode 100644 process/drivers/image_metadata.rs diff --git a/process/drivers.rs b/process/drivers.rs index 31174160..4db0523a 100644 --- a/process/drivers.rs +++ b/process/drivers.rs @@ -35,7 +35,6 @@ use self::{ docker_driver::DockerDriver, github_driver::GithubDriver, gitlab_driver::GitlabDriver, - image_metadata::ImageMetadata, local_driver::LocalDriver, opts::{ BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts, @@ -44,8 +43,8 @@ use self::{ podman_driver::PodmanDriver, skopeo_driver::SkopeoDriver, types::{ - BuildDriverType, CiDriverType, DetermineDriver, InspectDriverType, RunDriverType, - SigningDriverType, + BuildDriverType, CiDriverType, DetermineDriver, ImageMetadata, InspectDriverType, + RunDriverType, SigningDriverType, }, }; @@ -57,7 +56,6 @@ mod docker_driver; mod functions; mod github_driver; mod gitlab_driver; -pub mod image_metadata; mod local_driver; pub mod opts; mod podman_driver; diff --git a/process/drivers/docker_driver.rs b/process/drivers/docker_driver.rs index 675740d9..e587445c 100644 --- a/process/drivers/docker_driver.rs +++ b/process/drivers/docker_driver.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, env, io::Write, path::Path, @@ -9,15 +10,15 @@ use std::{ use blue_build_utils::{ cmd, - constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE}, + constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST}, credentials::Credentials, string_vec, - traits::IntoCollector, }; use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, info, trace, warn}; -use miette::{bail, IntoDiagnostic, Result}; +use miette::{bail, miette, IntoDiagnostic, Report, Result}; +use oci_distribution::Reference; use once_cell::sync::Lazy; use semver::Version; use serde::Deserialize; @@ -25,9 +26,9 @@ use tempdir::TempDir; use crate::{ drivers::{ - image_metadata::ImageMetadata, opts::{RunOptsEnv, RunOptsVolume}, - types::Platform, + types::{InspectDriverType, Platform}, + Driver, }, logging::{CommandLogging, Logger}, signal_handler::{add_cid, remove_cid, ContainerId, ContainerRuntime}, @@ -35,9 +36,53 @@ use crate::{ use super::{ opts::{BuildOpts, BuildTagPushOpts, GetMetadataOpts, PushOpts, RunOpts, TagOpts}, + types::ImageMetadata, BuildDriver, DriverVersion, InspectDriver, RunDriver, }; +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +struct DockerImageMetadata { + config: DockerImageMetadataConfig, + repo_digests: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +struct DockerImageMetadataConfig { + labels: HashMap, +} + +impl TryFrom> for ImageMetadata { + type Error = Report; + + fn try_from(mut value: Vec) -> Result { + if value.is_empty() { + bail!("Need at least one metadata entry:\n{value:?}"); + } + + let mut value = value.swap_remove(0); + if value.repo_digests.is_empty() { + bail!("Metadata requires at least 1 digest:\n{value:#?}"); + } + + let digest: Reference = value + .repo_digests + .swap_remove(0) + .parse() + .into_diagnostic()?; + let digest = digest + .digest() + .ok_or_else(|| miette!("Unable to read digest from {digest}"))? + .to_string(); + + Ok(Self { + labels: value.config.labels, + digest, + }) + } +} + #[derive(Debug, Deserialize)] struct DockerVerisonJsonClient { #[serde(alias = "Version")] @@ -280,7 +325,14 @@ impl BuildDriver for DockerDriver { format!( "type=image,name={first_image},push=true,compression={},oci-mediatypes=true", opts.compression - ) + ), + // Load the image to the local image registry + // if the inspect driver is docker so that + // we don't have to pull the image again to inspect. + if matches!( + Driver::get_inspect_driver(), + InspectDriverType::Docker, + ) => "--load", ); } else { cmd!(command, "--load"); @@ -318,34 +370,44 @@ impl BuildDriver for DockerDriver { impl InspectDriver for DockerDriver { fn get_metadata(opts: &GetMetadataOpts) -> Result { - trace!("DockerDriver::get_labels({opts:#?})"); + trace!("DockerDriver::get_metadata({opts:#?})"); let url = opts.tag.as_ref().map_or_else( - || format!("docker://{}", opts.image), - |tag| format!("docker://{}:{tag}", opts.image), + || format!("{}", opts.image), + |tag| format!("{}:{tag}", opts.image), ); 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 {}, pulling image...", + url.bold() + )), ); progress.enable_steady_tick(Duration::from_millis(100)); - let mut args = Vec::new(); - if !matches!(opts.platform, Platform::Native) { - args.extend(["--override-arch", opts.platform.arch()]); + let mut command = cmd!( + "docker", + "pull", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], + &url, + ); + trace!("{command:?}"); + + let output = command.output().into_diagnostic()?; + + if !output.status.success() { + bail!("Failed to pull {} for inspection!", url.bold()); } - args.extend(["inspect", &url]); - - let output = Self::run_output( - &RunOpts::builder() - .image(SKOPEO_IMAGE) - .args(args.collect_into_vec()) - .remove(true) - .build(), - ) - .into_diagnostic()?; + + let mut command = cmd!("docker", "image", "inspect", "--format=json", &url); + trace!("{command:?}"); + + let output = command.output().into_diagnostic()?; progress.finish_and_clear(); Logger::multi_progress().remove(&progress); @@ -356,7 +418,11 @@ impl InspectDriver for DockerDriver { bail!("Failed to inspect image {url}") } - serde_json::from_slice(&output.stdout).into_diagnostic() + serde_json::from_slice::>(&output.stdout) + .into_diagnostic() + .inspect(|metadata| trace!("{metadata:#?}")) + .and_then(ImageMetadata::try_from) + .inspect(|metadata| trace!("{metadata:#?}")) } } diff --git a/process/drivers/image_metadata.rs b/process/drivers/image_metadata.rs deleted file mode 100644 index 4ad06b0c..00000000 --- a/process/drivers/image_metadata.rs +++ /dev/null @@ -1,26 +0,0 @@ -use blue_build_utils::constants::IMAGE_VERSION_LABEL; -use serde::Deserialize; -use serde_json::Value; -use std::collections::HashMap; - -#[derive(Deserialize, Debug, Clone)] -pub struct ImageMetadata { - #[serde(alias = "Labels")] - pub labels: HashMap, - - #[serde(alias = "Digest")] - pub digest: String, -} - -impl ImageMetadata { - #[must_use] - pub fn get_version(&self) -> Option { - Some( - self.labels - .get(IMAGE_VERSION_LABEL)? - .as_str() - .and_then(|v| lenient_semver::parse(v).ok())? - .major, - ) - } -} diff --git a/process/drivers/podman_driver.rs b/process/drivers/podman_driver.rs index 685d4526..2538d462 100644 --- a/process/drivers/podman_driver.rs +++ b/process/drivers/podman_driver.rs @@ -1,25 +1,25 @@ use std::{ + collections::HashMap, io::Write, path::Path, process::{Command, ExitStatus, Stdio}, time::Duration, }; -use blue_build_utils::{ - cmd, constants::SKOPEO_IMAGE, credentials::Credentials, traits::IntoCollector, -}; +use blue_build_utils::{cmd, credentials::Credentials}; use colored::Colorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, trace, warn}; -use miette::{bail, miette, IntoDiagnostic, Result}; +use miette::{bail, miette, IntoDiagnostic, Report, Result}; +use oci_distribution::Reference; use semver::Version; use serde::Deserialize; use tempdir::TempDir; use crate::{ drivers::{ - image_metadata::ImageMetadata, opts::{RunOptsEnv, RunOptsVolume}, + types::ImageMetadata, types::Platform, }, logging::{CommandLogging, Logger}, @@ -31,6 +31,46 @@ use super::{ BuildDriver, DriverVersion, InspectDriver, RunDriver, }; +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +struct PodmanImageMetadata { + labels: HashMap, + repo_digests: Vec, +} + +impl TryFrom> for ImageMetadata { + type Error = Report; + + fn try_from(mut value: Vec) -> std::result::Result { + if value.is_empty() { + bail!("Podman inspection must have at least one metadata entry:\n{value:?}"); + } + if value.is_empty() { + bail!("Need at least one metadata entry:\n{value:?}"); + } + + let mut value = value.swap_remove(0); + if value.repo_digests.is_empty() { + bail!("Podman Metadata requires at least 1 digest:\n{value:#?}"); + } + + let digest: Reference = value + .repo_digests + .swap_remove(0) + .parse() + .into_diagnostic()?; + let digest = digest + .digest() + .ok_or_else(|| miette!("Unable to read digest from {digest}"))? + .to_string(); + + Ok(Self { + labels: value.labels, + digest, + }) + } +} + #[derive(Debug, Deserialize)] struct PodmanVersionJsonClient { #[serde(alias = "Version")] @@ -194,31 +234,41 @@ impl InspectDriver for PodmanDriver { trace!("PodmanDriver::get_metadata({opts:#?})"); let url = opts.tag.as_deref().map_or_else( - || format!("docker://{}", opts.image), - |tag| format!("docker://{}:{tag}", opts.image), + || format!("{}", opts.image), + |tag| format!("{}:{tag}", opts.image), ); 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 {}, pulling image...", + url.bold() + )), ); progress.enable_steady_tick(Duration::from_millis(100)); - let mut args = Vec::new(); - if !matches!(opts.platform, Platform::Native) { - args.extend(["--override-arch", opts.platform.arch()]); + let mut command = cmd!( + "podman", + "pull", + if !matches!(opts.platform, Platform::Native) => [ + "--platform", + opts.platform.to_string(), + ], + &url, + ); + trace!("{command:?}"); + + let output = command.output().into_diagnostic()?; + + if !output.status.success() { + bail!("Failed to pull {} for inspection!", url.bold()); } - args.extend(["inspect", &url]); - let output = Self::run_output( - &RunOpts::builder() - .image(SKOPEO_IMAGE) - .args(args.collect_into_vec()) - .remove(true) - .build(), - ) - .into_diagnostic()?; + let mut command = cmd!("podman", "image", "inspect", "--format=json", &url); + trace!("{command:?}"); + + let output = command.output().into_diagnostic()?; progress.finish_and_clear(); Logger::multi_progress().remove(&progress); @@ -228,7 +278,11 @@ impl InspectDriver for PodmanDriver { } else { bail!("Failed to inspect image {url}"); } - serde_json::from_slice(&output.stdout).into_diagnostic() + serde_json::from_slice::>(&output.stdout) + .into_diagnostic() + .inspect(|metadata| trace!("{metadata:#?}")) + .and_then(TryFrom::try_from) + .inspect(|metadata| trace!("{metadata:#?}")) } } diff --git a/process/drivers/skopeo_driver.rs b/process/drivers/skopeo_driver.rs index b54351ce..af68d17f 100644 --- a/process/drivers/skopeo_driver.rs +++ b/process/drivers/skopeo_driver.rs @@ -8,7 +8,7 @@ use miette::{bail, IntoDiagnostic, Result}; use crate::{drivers::types::Platform, logging::Logger}; -use super::{image_metadata::ImageMetadata, opts::GetMetadataOpts, InspectDriver}; +use super::{opts::GetMetadataOpts, types::ImageMetadata, InspectDriver}; #[derive(Debug)] pub struct SkopeoDriver; diff --git a/process/drivers/traits.rs b/process/drivers/traits.rs index 49dde33b..305e6e33 100644 --- a/process/drivers/traits.rs +++ b/process/drivers/traits.rs @@ -13,12 +13,12 @@ use semver::{Version, VersionReq}; use crate::drivers::{functions::get_private_key, types::CiDriverType, Driver}; use super::{ - image_metadata::ImageMetadata, opts::{ BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateImageNameOpts, GenerateKeyPairOpts, GenerateTagsOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, SignVerifyOpts, TagOpts, VerifyOpts, VerifyType, }, + types::ImageMetadata, }; /// Trait for retrieving version of a driver. diff --git a/process/drivers/types.rs b/process/drivers/types.rs index 8ae5fd84..4285ea26 100644 --- a/process/drivers/types.rs +++ b/process/drivers/types.rs @@ -1,8 +1,10 @@ -use std::env; +use std::{collections::HashMap, env}; -use blue_build_utils::constants::{GITHUB_ACTIONS, GITLAB_CI}; +use blue_build_utils::constants::{GITHUB_ACTIONS, GITLAB_CI, IMAGE_VERSION_LABEL}; use clap::ValueEnum; use log::trace; +use serde::Deserialize; +use serde_json::Value; use crate::drivers::{ buildah_driver::BuildahDriver, docker_driver::DockerDriver, podman_driver::PodmanDriver, @@ -205,3 +207,23 @@ impl std::fmt::Display for Platform { ) } } + +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct ImageMetadata { + pub labels: HashMap, + pub digest: String, +} + +impl ImageMetadata { + #[must_use] + pub fn get_version(&self) -> Option { + Some( + self.labels + .get(IMAGE_VERSION_LABEL)? + .as_str() + .and_then(|v| lenient_semver::parse(v).ok())? + .major, + ) + } +} diff --git a/src/commands/build.rs b/src/commands/build.rs index 4ee7ea4b..c0fc116b 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -179,6 +179,7 @@ impl BlueBuildCommand for BuildCommand { } else { PathBuf::from(CONTAINER_FILE) }) + .platform(self.platform) .recipe(recipe) .drivers(self.drivers) .build()