//! This module is responsible for managing various strategies //! to perform actions throughout the program. This hides all //! the implementation details from the command logic and allows //! for caching certain long execution tasks like inspecting the //! labels for an image. use std::{ collections::{hash_map::Entry, HashMap}, fmt::Debug, process::{ExitStatus, Output}, sync::{Mutex, RwLock}, }; use blue_build_recipe::Recipe; use blue_build_utils::constants::IMAGE_VERSION_LABEL; use clap::Args; use log::{debug, info, trace}; use miette::{miette, Result}; use once_cell::sync::Lazy; #[cfg(feature = "sigstore")] use sigstore_driver::SigstoreDriver; use typed_builder::TypedBuilder; use uuid::Uuid; use self::{ buildah_driver::BuildahDriver, cosign_driver::CosignDriver, docker_driver::DockerDriver, github_driver::GithubDriver, gitlab_driver::GitlabDriver, image_metadata::ImageMetadata, local_driver::LocalDriver, opts::{ BuildOpts, BuildTagPushOpts, CheckKeyPairOpts, GenerateKeyPairOpts, GetMetadataOpts, PushOpts, RunOpts, SignOpts, TagOpts, VerifyOpts, }, podman_driver::PodmanDriver, skopeo_driver::SkopeoDriver, types::{ BuildDriverType, CiDriverType, DetermineDriver, InspectDriverType, RunDriverType, SigningDriverType, }, }; pub use traits::*; mod buildah_driver; mod cosign_driver; mod docker_driver; mod functions; mod github_driver; mod gitlab_driver; pub mod image_metadata; mod local_driver; pub mod opts; mod podman_driver; #[cfg(feature = "sigstore")] mod sigstore_driver; mod skopeo_driver; mod traits; pub mod types; static INIT: Lazy> = Lazy::new(|| Mutex::new(false)); static SELECTED_BUILD_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); static SELECTED_INSPECT_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); static SELECTED_RUN_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); static SELECTED_SIGNING_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); static SELECTED_CI_DRIVER: Lazy>> = Lazy::new(|| RwLock::new(None)); /// UUID used to mark the current builds static BUILD_ID: Lazy = Lazy::new(Uuid::new_v4); /// The cached os versions static OS_VERSION: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); /// Args for selecting the various drivers to use for runtime. /// /// If the args are left uninitialized, the program will determine /// the best one available. #[derive(Default, Clone, Copy, Debug, TypedBuilder, Args)] pub struct DriverArgs { /// Select which driver to use to build /// your image. #[builder(default)] #[arg(short = 'B', long)] build_driver: Option, /// Select which driver to use to inspect /// images. #[builder(default)] #[arg(short = 'I', long)] inspect_driver: Option, /// Select which driver to use to sign /// images. #[builder(default)] #[arg(short = 'S', long)] signing_driver: Option, /// Select which driver to use to run /// containers. #[builder(default)] #[arg(short = 'R', long)] run_driver: Option, } macro_rules! impl_driver_type { ($cache:ident) => {{ let lock = $cache.read().expect("Should read"); lock.expect("Driver should have initialized build driver") }}; } macro_rules! impl_driver_init { (@) => { }; ($init:ident; $($tail:tt)*) => { { let mut initialized = $init.lock().expect("Must lock INIT"); if !*initialized { impl_driver_init!(@ $($tail)*); *initialized = true; } } }; (@ default => $cache:ident; $($tail:tt)*) => { { let mut driver = $cache.write().expect("Should lock"); impl_driver_init!(@ $($tail)*); *driver = Some(driver.determine_driver()); ::log::trace!("Driver set {driver:?}"); drop(driver); } }; (@ $driver:expr => $cache:ident; $($tail:tt)*) => { { let mut driver = $cache.write().expect("Should lock"); impl_driver_init!(@ $($tail)*); *driver = Some($driver.determine_driver()); ::log::trace!("Driver set {driver:?}"); drop(driver); } }; } pub struct Driver; impl Driver { /// Initializes the Strategy with user provided credentials. /// /// If you want to take advantage of a user's credentials, /// you will want to run init before trying to use any of /// the strategies. /// /// # Panics /// Will panic if it is unable to initialize drivers. pub fn init(mut args: DriverArgs) { trace!("Driver::init()"); impl_driver_init! { INIT; args.build_driver => SELECTED_BUILD_DRIVER; args.inspect_driver => SELECTED_INSPECT_DRIVER; args.run_driver => SELECTED_RUN_DRIVER; args.signing_driver => SELECTED_SIGNING_DRIVER; default => SELECTED_CI_DRIVER; } } /// Gets the current build's UUID #[must_use] pub fn get_build_id() -> Uuid { trace!("Driver::get_build_id()"); *BUILD_ID } /// Retrieve the `os_version` for an image. /// /// This gets cached for faster resolution if it's required /// in another part of the program. /// /// # Errors /// Will error if the image doesn't have OS version info /// or we are unable to lock a mutex. /// /// # Panics /// Panics if the mutex fails to lock. pub fn get_os_version(recipe: &Recipe) -> Result { #[cfg(test)] { use miette::IntoDiagnostic; let _ = recipe; // silence lint if true { return crate::test::create_test_recipe() .image_version .parse() .into_diagnostic(); } } trace!("Driver::get_os_version({recipe:#?})"); let image = format!("{}:{}", &recipe.base_image, &recipe.image_version); let mut os_version_lock = OS_VERSION.lock().expect("Should lock"); let entry = os_version_lock.get(&image); let os_version = match entry { None => { info!("Retrieving OS version from {image}. This might take a bit"); let inspect_opts = GetMetadataOpts::builder() .image(&*recipe.base_image) .tag(&*recipe.image_version) .build(); let inspection = Self::get_metadata(&inspect_opts)?; let os_version = inspection.get_version().ok_or_else(|| { miette!( help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."), "Unable to get the OS version from the labels" ) })?; trace!("os_version: {os_version}"); os_version } Some(os_version) => { debug!("Found cached {os_version} for {image}"); *os_version } }; if let Entry::Vacant(entry) = os_version_lock.entry(image.clone()) { trace!("Caching version {os_version} for {image}"); entry.insert(os_version); } drop(os_version_lock); Ok(os_version) } fn get_build_driver() -> BuildDriverType { impl_driver_type!(SELECTED_BUILD_DRIVER) } fn get_inspect_driver() -> InspectDriverType { impl_driver_type!(SELECTED_INSPECT_DRIVER) } fn get_signing_driver() -> SigningDriverType { impl_driver_type!(SELECTED_SIGNING_DRIVER) } fn get_run_driver() -> RunDriverType { impl_driver_type!(SELECTED_RUN_DRIVER) } fn get_ci_driver() -> CiDriverType { impl_driver_type!(SELECTED_CI_DRIVER) } } macro_rules! impl_build_driver { ($func:ident($($args:expr),*)) => { match Self::get_build_driver() { BuildDriverType::Buildah => BuildahDriver::$func($($args,)*), BuildDriverType::Podman => PodmanDriver::$func($($args,)*), BuildDriverType::Docker => DockerDriver::$func($($args,)*), } }; } impl BuildDriver for Driver { fn build(opts: &BuildOpts) -> Result<()> { impl_build_driver!(build(opts)) } fn tag(opts: &TagOpts) -> Result<()> { impl_build_driver!(tag(opts)) } fn push(opts: &PushOpts) -> Result<()> { impl_build_driver!(push(opts)) } fn login() -> Result<()> { impl_build_driver!(login()) } fn build_tag_push(opts: &BuildTagPushOpts) -> Result<()> { impl_build_driver!(build_tag_push(opts)) } } macro_rules! impl_signing_driver { ($func:ident($($args:expr),*)) => { match Self::get_signing_driver() { SigningDriverType::Cosign => CosignDriver::$func($($args,)*), #[cfg(feature = "sigstore")] SigningDriverType::Sigstore => SigstoreDriver::$func($($args,)*), } }; } impl SigningDriver for Driver { fn generate_key_pair(opts: &GenerateKeyPairOpts) -> Result<()> { impl_signing_driver!(generate_key_pair(opts)) } fn check_signing_files(opts: &CheckKeyPairOpts) -> Result<()> { impl_signing_driver!(check_signing_files(opts)) } fn sign(opts: &SignOpts) -> Result<()> { impl_signing_driver!(sign(opts)) } fn verify(opts: &VerifyOpts) -> Result<()> { impl_signing_driver!(verify(opts)) } fn signing_login() -> Result<()> { impl_signing_driver!(signing_login()) } } macro_rules! impl_inspect_driver { ($func:ident($($args:expr),*)) => { match Self::get_inspect_driver() { InspectDriverType::Skopeo => SkopeoDriver::$func($($args,)*), InspectDriverType::Podman => PodmanDriver::$func($($args,)*), InspectDriverType::Docker => DockerDriver::$func($($args,)*), } }; } impl InspectDriver for Driver { fn get_metadata(opts: &GetMetadataOpts) -> Result { impl_inspect_driver!(get_metadata(opts)) } } macro_rules! impl_run_driver { ($func:ident($($args:expr),*)) => { match Self::get_run_driver() { RunDriverType::Docker => DockerDriver::$func($($args,)*), RunDriverType::Podman => PodmanDriver::$func($($args,)*), } }; } impl RunDriver for Driver { fn run(opts: &RunOpts) -> std::io::Result { impl_run_driver!(run(opts)) } fn run_output(opts: &RunOpts) -> std::io::Result { impl_run_driver!(run_output(opts)) } } macro_rules! impl_ci_driver { ($func:ident($($args:expr),*)) => { match Self::get_ci_driver() { CiDriverType::Local => LocalDriver::$func($($args)*), CiDriverType::Gitlab => GitlabDriver::$func($($args)*), CiDriverType::Github => GithubDriver::$func($($args)*), } }; } impl CiDriver for Driver { fn on_default_branch() -> bool { impl_ci_driver!(on_default_branch()) } fn keyless_cert_identity() -> Result { impl_ci_driver!(keyless_cert_identity()) } fn oidc_provider() -> Result { impl_ci_driver!(oidc_provider()) } fn generate_tags(recipe: &Recipe) -> Result> { impl_ci_driver!(generate_tags(recipe)) } fn get_repo_url() -> Result { impl_ci_driver!(get_repo_url()) } fn get_registry() -> Result { impl_ci_driver!(get_registry()) } fn generate_image_name(recipe: &Recipe) -> Result { impl_ci_driver!(generate_image_name(recipe)) } }