From e9c96e204d9ad45e9196f0aed8d377c1191e4553 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Sun, 7 Apr 2024 22:15:33 -0400 Subject: [PATCH] feat: Add driver selection args (#153) There are 2 new args available that allow the user to specify which program to use for building and inspecting images. If the user doesn't provide an argument, the tool will determine which program to use like it has been. Help text: ``` Build an image from a recipe Usage: bluebuild build [OPTIONS] [RECIPE] Arguments: [RECIPE] The recipe file to build an image Options: -p, --push Push the image with all the tags. Requires `--registry`, `--username`, and `--password` if not building in CI. -c, --compression-format The compression format the images will be pushed in [default: gzip] [possible values: gzip, zstd] -n, --no-retry-push Block `bluebuild` from retrying to push the image --retry-count The number of times to retry pushing the image [default: 1] -f, --force Allow `bluebuild` to overwrite an existing Containerfile without confirmation. This is not needed if the Containerfile is in .gitignore or has already been built by `bluebuild`. -a, --archive Archives the built image into a tarfile in the specified directory --registry The registry's domain name -v, --verbose... Increase logging verbosity -q, --quiet... Decrease logging verbosity --registry-namespace The url path to your base project images [aliases: registry-path] -U, --username The username to login to the container registry -P, --password The password to login to the container registry -B, --build-driver Select which driver to use to build your image [possible values: buildah, podman, docker] -I, --inspect-driver Select which driver to use to inspect images [possible values: skopeo, podman, docker] -h, --help Print help (see a summary with '-h') ``` --- src/commands.rs | 23 ++++++- src/commands/build.rs | 8 ++- src/commands/template.rs | 12 +++- src/drivers.rs | 127 ++++++++++++++++++++++++++------------- src/drivers/types.rs | 15 +++++ 5 files changed, 138 insertions(+), 47 deletions(-) create mode 100644 src/drivers/types.rs diff --git a/src/commands.rs b/src/commands.rs index f5c211fd..f835b2be 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,9 +1,13 @@ use log::error; -use clap::{command, crate_authors, Parser, Subcommand}; +use clap::{command, crate_authors, Args, Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; +use typed_builder::TypedBuilder; -use crate::shadow; +use crate::{ + drivers::types::{BuildDriverType, InspectDriverType}, + shadow, +}; pub mod bug_report; pub mod build; @@ -92,3 +96,18 @@ pub enum CommandArgs { /// Generate shell completions for your shell to stdout Completions(completions::CompletionsCommand), } + +#[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, +} diff --git a/src/commands/build.rs b/src/commands/build.rs index 50757bdd..2bcdd446 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -27,7 +27,7 @@ use crate::{ }, }; -use super::BlueBuildCommand; +use super::{BlueBuildCommand, DriverArgs}; #[derive(Debug, Clone, Args, TypedBuilder)] pub struct BuildCommand { @@ -99,6 +99,10 @@ pub struct BuildCommand { #[arg(short = 'P', long)] #[builder(default, setter(into, strip_option))] password: Option, + + #[clap(flatten)] + #[builder(default)] + drivers: DriverArgs, } impl BlueBuildCommand for BuildCommand { @@ -110,6 +114,8 @@ impl BlueBuildCommand for BuildCommand { .username(self.username.as_ref()) .password(self.password.as_ref()) .registry(self.registry.as_ref()) + .build_driver(self.drivers.build_driver) + .inspect_driver(self.drivers.inspect_driver) .build() .init()?; diff --git a/src/commands/template.rs b/src/commands/template.rs index e9cbaf7b..c4062f07 100644 --- a/src/commands/template.rs +++ b/src/commands/template.rs @@ -12,7 +12,7 @@ use typed_builder::TypedBuilder; use crate::drivers::Driver; -use super::BlueBuildCommand; +use super::{BlueBuildCommand, DriverArgs}; #[derive(Debug, Clone, Args, TypedBuilder)] pub struct TemplateCommand { @@ -41,6 +41,10 @@ pub struct TemplateCommand { #[arg(long)] #[builder(default, setter(into, strip_option))] registry_namespace: Option, + + #[clap(flatten)] + #[builder(default)] + drivers: DriverArgs, } impl BlueBuildCommand for TemplateCommand { @@ -53,6 +57,12 @@ impl BlueBuildCommand for TemplateCommand { .display() ); + Driver::builder() + .build_driver(self.drivers.build_driver) + .inspect_driver(self.drivers.inspect_driver) + .build() + .init()?; + self.template_file() } } diff --git a/src/drivers.rs b/src/drivers.rs index c3f8d308..19de4acb 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -6,14 +6,13 @@ use std::{ collections::{hash_map::Entry, HashMap}, - process, sync::{Arc, Mutex}, }; use anyhow::{anyhow, bail, Result}; use blue_build_recipe::Recipe; use blue_build_utils::constants::IMAGE_VERSION_LABEL; -use log::{debug, error, info, trace}; +use log::{debug, info, trace}; use once_cell::sync::Lazy; use semver::{Version, VersionReq}; use typed_builder::TypedBuilder; @@ -27,6 +26,7 @@ use self::{ opts::{BuildTagPushOpts, CompressionType}, podman_driver::PodmanDriver, skopeo_driver::SkopeoDriver, + types::{BuildDriverType, InspectDriverType}, }; mod buildah_driver; @@ -34,46 +34,62 @@ mod docker_driver; pub mod opts; mod podman_driver; mod skopeo_driver; +pub mod types; -/// Stores the build strategy. +static INIT: Lazy> = Lazy::new(|| Mutex::new(())); +static SELECTED_BUILD_DRIVER: Lazy>> = Lazy::new(|| Mutex::new(None)); +static SELECTED_INSPECT_DRIVER: Lazy>> = + Lazy::new(|| Mutex::new(None)); + +/// Stores the build driver. /// /// This will, on load, find the best way to build in the /// current environment. Once that strategy is determined, /// it will be available for any part of the program to call /// on to perform builds. /// -/// # Exits +/// # Panics /// -/// This will cause the program to exit if a build strategy could +/// This will cause a panic if a build strategy could /// not be determined. -static BUILD_STRATEGY: Lazy> = - Lazy::new(|| match Driver::determine_build_driver() { - Err(e) => { - error!("{e}"); - process::exit(1); - } - Ok(strat) => strat, - }); +static BUILD_DRIVER: Lazy> = Lazy::new(|| { + let driver = SELECTED_BUILD_DRIVER.lock().unwrap(); + driver.map_or_else( + || panic!("Driver needs to be initialized"), + |driver| -> Arc { + match driver { + BuildDriverType::Buildah => Arc::new(BuildahDriver), + BuildDriverType::Podman => Arc::new(PodmanDriver), + BuildDriverType::Docker => Arc::new(DockerDriver), + } + }, + ) +}); -/// Stores the inspection strategy. +/// Stores the inspection driver. /// /// This will, on load, find the best way to inspect images in the /// current environment. Once that strategy is determined, /// it will be available for any part of the program to call /// on to perform inspections. /// -/// # Exits +/// # Panics /// -/// This will cause the program to exit if a build strategy could +/// This will cause a panic if a build strategy could /// not be determined. -static INSPECT_STRATEGY: Lazy> = - Lazy::new(|| match Driver::determine_inspect_driver() { - Err(e) => { - error!("{e}"); - process::exit(1); - } - Ok(strat) => strat, - }); +static INSPECT_DRIVER: Lazy> = Lazy::new(|| { + let driver = SELECTED_INSPECT_DRIVER.lock().unwrap(); + driver.map_or_else( + || panic!("Driver needs to be initialized"), + |driver| -> Arc { + match driver { + InspectDriverType::Skopeo => Arc::new(SkopeoDriver), + InspectDriverType::Podman => Arc::new(PodmanDriver), + InspectDriverType::Docker => Arc::new(DockerDriver), + } + }, + ) +}); /// UUID used to mark the current builds static BUILD_ID: Lazy = Lazy::new(Uuid::new_v4); @@ -205,6 +221,12 @@ pub struct Driver<'a> { #[builder(default)] registry: Option<&'a String>, + + #[builder(default)] + build_driver: Option, + + #[builder(default)] + inspect_driver: Option, } impl Driver<'_> { @@ -218,7 +240,30 @@ impl Driver<'_> { /// Will error if it is unable to set the user credentials. pub fn init(self) -> Result<()> { trace!("Driver::init()"); + let init = INIT.lock().map_err(|e| anyhow!("{e}"))?; credentials::set_user_creds(self.username, self.password, self.registry)?; + + let mut build_driver = SELECTED_BUILD_DRIVER.lock().map_err(|e| anyhow!("{e}"))?; + let mut inspect_driver = SELECTED_INSPECT_DRIVER.lock().map_err(|e| anyhow!("{e}"))?; + + *build_driver = Some(match self.build_driver { + None => Self::determine_build_driver()?, + Some(driver) => driver, + }); + trace!("Build driver set to {:?}", *build_driver); + drop(build_driver); + let _ = Self::get_build_driver(); + + *inspect_driver = Some(match self.inspect_driver { + None => Self::determine_inspect_driver()?, + Some(driver) => driver, + }); + trace!("Inspect driver set to {:?}", *inspect_driver); + drop(inspect_driver); + let _ = Self::get_inspection_driver(); + + drop(init); + Ok(()) } @@ -232,13 +277,13 @@ impl Driver<'_> { /// Gets the current run's build strategy pub fn get_build_driver() -> Arc { trace!("Driver::get_build_driver()"); - BUILD_STRATEGY.clone() + BUILD_DRIVER.clone() } /// Gets the current run's inspectioin strategy pub fn get_inspection_driver() -> Arc { trace!("Driver::get_inspection_driver()"); - INSPECT_STRATEGY.clone() + INSPECT_DRIVER.clone() } /// Retrieve the `os_version` for an image. @@ -263,7 +308,7 @@ impl Driver<'_> { None => { info!("Retrieving OS version from {image}. This might take a bit"); let inspection = - INSPECT_STRATEGY.get_metadata(&recipe.base_image, &recipe.image_version)?; + INSPECT_DRIVER.get_metadata(&recipe.base_image, &recipe.image_version)?; let os_version = inspection.get_version().ok_or_else(|| { anyhow!( @@ -288,39 +333,37 @@ impl Driver<'_> { Ok(os_version) } - fn determine_inspect_driver() -> Result> { + fn determine_inspect_driver() -> Result { trace!("Driver::determine_inspect_driver()"); - let driver: Arc = match ( + Ok(match ( blue_build_utils::check_command_exists("skopeo"), blue_build_utils::check_command_exists("docker"), blue_build_utils::check_command_exists("podman"), ) { - (Ok(_skopeo), _, _) => Arc::new(SkopeoDriver), - (_, Ok(_docker), _) => Arc::new(DockerDriver), - (_, _, Ok(_podman)) => Arc::new(PodmanDriver), + (Ok(_skopeo), _, _) => InspectDriverType::Skopeo, + (_, Ok(_docker), _) => InspectDriverType::Docker, + (_, _, Ok(_podman)) => InspectDriverType::Podman, _ => bail!("Could not determine inspection strategy. You need either skopeo, docker, or podman"), - }; - - Ok(driver) + }) } - fn determine_build_driver() -> Result> { + fn determine_build_driver() -> Result { trace!("Driver::determine_build_driver()"); - let driver: Arc = match ( + Ok(match ( blue_build_utils::check_command_exists("docker"), blue_build_utils::check_command_exists("podman"), blue_build_utils::check_command_exists("buildah"), ) { (Ok(_docker), _, _) if DockerDriver::is_supported_version() => { - Arc::new(DockerDriver) + BuildDriverType::Docker } (_, Ok(_podman), _) if PodmanDriver::is_supported_version() => { - Arc::new(PodmanDriver) + BuildDriverType::Podman } (_, _, Ok(_buildah)) if BuildahDriver::is_supported_version() => { - Arc::new(BuildahDriver) + BuildDriverType::Buildah } _ => bail!( "Could not determine strategy, need either docker version {}, podman version {}, or buildah version {} to continue", @@ -328,8 +371,6 @@ impl Driver<'_> { PodmanDriver::VERSION_REQ, BuildahDriver::VERSION_REQ, ), - }; - - Ok(driver) + }) } } diff --git a/src/drivers/types.rs b/src/drivers/types.rs new file mode 100644 index 00000000..a1491653 --- /dev/null +++ b/src/drivers/types.rs @@ -0,0 +1,15 @@ +use clap::ValueEnum; + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum InspectDriverType { + Skopeo, + Podman, + Docker, +} + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum BuildDriverType { + Buildah, + Podman, + Docker, +}