diff --git a/Cargo.toml b/Cargo.toml index 03d171c..20ccd13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ suspicious = "warn" perf = "warn" style = "warn" nursery = "warn" +pedantic = "warn" +module_name_repetitions = "allow" [package] name = "blue-build" diff --git a/recipe/src/module.rs b/recipe/src/module.rs index bcef920..17d23c4 100644 --- a/recipe/src/module.rs +++ b/recipe/src/module.rs @@ -73,6 +73,7 @@ impl<'a> Module<'a> { self.get_module_type_list("containerfile", "snippets") } + #[must_use] pub fn print_module_context(&'a self) -> String { serde_json::to_string(self).unwrap_or_else(|e| { error!("Failed to parse module!!!!!: {e}"); @@ -80,6 +81,7 @@ impl<'a> Module<'a> { }) } + #[must_use] pub fn get_files_list(&'a self) -> Option> { Some( self.config diff --git a/recipe/src/module_ext.rs b/recipe/src/module_ext.rs index 53d8496..1dbdbe8 100644 --- a/recipe/src/module_ext.rs +++ b/recipe/src/module_ext.rs @@ -39,6 +39,7 @@ impl ModuleExt<'_> { ) } + #[must_use] pub fn get_akmods_info_list(&self, os_version: &str) -> Vec { trace!("get_akmods_image_list({self:#?}, {os_version})"); diff --git a/recipe/src/recipe.rs b/recipe/src/recipe.rs index 6a90936..27caff9 100644 --- a/recipe/src/recipe.rs +++ b/recipe/src/recipe.rs @@ -1,7 +1,10 @@ use std::{borrow::Cow, env, fs, path::Path}; use anyhow::Result; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID, + CI_PIPELINE_SOURCE, GITHUB_EVENT_NAME, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER, +}; use chrono::Local; use indexmap::IndexMap; use log::{debug, trace, warn}; diff --git a/src/bin/bluebuild.rs b/src/bin/bluebuild.rs index 7d0eaa3..d373549 100644 --- a/src/bin/bluebuild.rs +++ b/src/bin/bluebuild.rs @@ -1,4 +1,4 @@ -use blue_build::commands::*; +use blue_build::commands::{BlueBuildArgs, BlueBuildCommand, CommandArgs}; use clap::Parser; use env_logger::WriteStyle; diff --git a/src/commands/bug_report.rs b/src/commands/bug_report.rs index be5dcdd..355ae1a 100644 --- a/src/commands/bug_report.rs +++ b/src/commands/bug_report.rs @@ -1,6 +1,9 @@ use blue_build_recipe::Recipe; use blue_build_template::{GithubIssueTemplate, Template}; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + BUG_REPORT_WARNING_MESSAGE, GITHUB_CHAR_LIMIT, LC_TERMINAL, LC_TERMINAL_VERSION, TERM_PROGRAM, + TERM_PROGRAM_VERSION, UNKNOWN_SHELL, UNKNOWN_TERMINAL, UNKNOWN_VERSION, +}; use clap::Args; use clap_complete::Shell; use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; @@ -82,7 +85,6 @@ impl BugReportCommand { .color(Colors::BrightWhiteFg) ); - const WARNING_MESSAGE: &str = "Please copy the above report and open an issue manually."; let question = requestty::Question::confirm("anonymous") .message( "Forward the pre-filled report above to GitHub in your browser?" @@ -103,11 +105,11 @@ impl BugReportCommand { return Err(e.into()); } } else { - println!("{WARNING_MESSAGE}"); + println!("{BUG_REPORT_WARNING_MESSAGE}"); } } Err(_) => { - println!("Will not open an issue in your browser! {WARNING_MESSAGE}"); + println!("Will not open an issue in your browser! {BUG_REPORT_WARNING_MESSAGE}"); } } diff --git a/src/commands/build.rs b/src/commands/build.rs index dea459b..f67e610 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -6,7 +6,13 @@ use std::{ use anyhow::{bail, Result}; use blue_build_recipe::Recipe; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + ARCHIVE_SUFFIX, BUILD_ID_LABEL, CI_DEFAULT_BRANCH, CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, + CI_PROJECT_URL, CI_REGISTRY, CI_SERVER_HOST, CI_SERVER_PROTOCOL, CONTAINER_FILE, COSIGN_PATH, + COSIGN_PRIVATE_KEY, GITHUB_REPOSITORY_OWNER, GITHUB_TOKEN, GITHUB_TOKEN_ISSUER_URL, + GITHUB_WORKFLOW_REF, GITIGNORE_PATH, LABELED_ERROR_MESSAGE, NO_LABEL_ERROR_MESSAGE, + RECIPE_PATH, SIGSTORE_ID_TOKEN, +}; use clap::Args; use colorized::{Color, Colors}; use log::{debug, info, trace, warn}; @@ -165,7 +171,7 @@ impl BlueBuildCommand for BuildCommand { if !is_ignored { let containerfile = fs::read_to_string(container_file_path)?; let has_label = containerfile.lines().any(|line| { - let label = format!("LABEL {}", BUILD_ID_LABEL); + let label = format!("LABEL {BUILD_ID_LABEL}"); line.to_string().trim().starts_with(&label) }); @@ -184,8 +190,8 @@ impl BlueBuildCommand for BuildCommand { if let Ok(answer) = requestty::prompt_one(question) { if answer.as_bool().unwrap_or(false) { blue_build_utils::append_to_file( - GITIGNORE_PATH, - &format!("/{}", CONTAINER_FILE), + &GITIGNORE_PATH, + &format!("/{CONTAINER_FILE}"), )?; } } @@ -229,7 +235,7 @@ impl BuildCommand { let image_name = self.generate_full_image_name(&recipe)?; if self.push { - self.login()?; + Self::login()?; } self.run_build(&image_name, &tags)?; @@ -239,7 +245,7 @@ impl BuildCommand { Ok(()) } - fn login(&self) -> Result<()> { + fn login() -> Result<()> { trace!("BuildCommand::login()"); info!("Attempting to login to the registry"); @@ -370,10 +376,10 @@ impl BuildCommand { strat.tag(&full_image, image_name, tag)?; if self.push { - let retry_count = if !self.no_retry_push { - self.retry_count - } else { + let retry_count = if self.no_retry_push { 0 + } else { + self.retry_count }; debug!("Pushing all images"); @@ -427,33 +433,7 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> { (_, _, _, _, _, _, _, Ok(cosign_private_key)) if !cosign_private_key.is_empty() && Path::new(COSIGN_PATH).exists() => { - info!("Signing image: {image_digest}"); - - trace!("cosign sign --key=env://COSIGN_PRIVATE_KEY {image_digest}"); - - if Command::new("cosign") - .arg("sign") - .arg("--key=env://COSIGN_PRIVATE_KEY") - .arg(&image_digest) - .status()? - .success() - { - info!("Successfully signed image!"); - } else { - bail!("Failed to sign image: {image_digest}"); - } - - trace!("cosign verify --key {COSIGN_PATH} {image_name_tag}"); - - if !Command::new("cosign") - .arg("verify") - .arg(format!("--key={COSIGN_PATH}")) - .arg(&image_name_tag) - .status()? - .success() - { - bail!("Failed to verify image!"); - } + sign_priv_public_pair(&image_digest, &image_name_tag)?; } // Gitlab keyless ( @@ -541,6 +521,38 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> { Ok(()) } +fn sign_priv_public_pair(image_digest: &str, image_name_tag: &str) -> Result<()> { + info!("Signing image: {image_digest}"); + + trace!("cosign sign --key=env://{COSIGN_PRIVATE_KEY} {image_digest}"); + + if Command::new("cosign") + .arg("sign") + .arg("--key=env://COSIGN_PRIVATE_KEY") + .arg(image_digest) + .status()? + .success() + { + info!("Successfully signed image!"); + } else { + bail!("Failed to sign image: {image_digest}"); + } + + trace!("cosign verify --key {COSIGN_PATH} {image_name_tag}"); + + if !Command::new("cosign") + .arg("verify") + .arg(format!("--key={COSIGN_PATH}")) + .arg(image_name_tag) + .status()? + .success() + { + bail!("Failed to verify image!"); + } + + Ok(()) +} + fn get_image_digest(image_name: &str, tag: Option<&str>) -> Result { trace!("get_image_digest({image_name}, {tag:?})"); diff --git a/src/commands/local.rs b/src/commands/local.rs index 328487c..ec11118 100644 --- a/src/commands/local.rs +++ b/src/commands/local.rs @@ -6,7 +6,7 @@ use std::{ use anyhow::{bail, Result}; use blue_build_recipe::Recipe; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ARCHIVE_SUFFIX, LOCAL_BUILD}; use clap::Args; use log::{debug, info, trace}; use typed_builder::TypedBuilder; diff --git a/src/commands/template.rs b/src/commands/template.rs index c555995..efedca8 100644 --- a/src/commands/template.rs +++ b/src/commands/template.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use anyhow::Result; use blue_build_recipe::Recipe; use blue_build_template::{ContainerFileTemplate, Template}; -use blue_build_utils::constants::*; +use blue_build_utils::constants::RECIPE_PATH; use clap::Args; use log::{debug, info, trace}; use typed_builder::TypedBuilder; diff --git a/src/lib.rs b/src/lib.rs index ed4a321..b7cada7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ //! The root library for blue-build. #![doc = include_str!("../README.md")] +#![allow(clippy::needless_raw_string_hashes)] shadow_rs::shadow!(shadow); diff --git a/src/strategies.rs b/src/strategies.rs index 67f7253..fed32e8 100644 --- a/src/strategies.rs +++ b/src/strategies.rs @@ -14,7 +14,10 @@ use std::{ use anyhow::{anyhow, bail, Result}; use blue_build_recipe::Recipe; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + IMAGE_VERSION_LABEL, RUN_PODMAN_SOCK, VAR_RUN_PODMAN_PODMAN_SOCK, VAR_RUN_PODMAN_SOCK, + XDG_RUNTIME_DIR, +}; pub use credentials::Credentials; use log::{debug, error, info, trace}; use once_cell::sync::Lazy; @@ -94,17 +97,37 @@ static OS_VERSION: Lazy>> = Lazy::new(|| Mutex::ne /// Allows agnostic building, tagging /// pushing, and login. pub trait BuildStrategy: Sync + Send { + /// Runs the build logic for the strategy. + /// + /// # Errors + /// Will error if the build fails. fn build(&self, image: &str) -> Result<()>; + /// Runs the tag logic for the strategy. + /// + /// # Errors + /// Will error if the tagging fails. fn tag(&self, src_image: &str, image_name: &str, tag: &str) -> Result<()>; + /// Runs the push logic for the strategy + /// + /// # Errors + /// Will error if the push fails. fn push(&self, image: &str) -> Result<()>; + /// Runs the login logic for the strategy. + /// + /// # Errors + /// Will error if login fails. fn login(&self) -> Result<()>; } /// Allows agnostic inspection of images. pub trait InspectStrategy: Sync + Send { + /// Gets the labels on an image tag. + /// + /// # Errors + /// Will error if it is unable to get the labels. fn get_labels(&self, image_name: &str, tag: &str) -> Result; } @@ -121,12 +144,21 @@ pub struct Strategy<'a> { } impl<'a> Strategy<'a> { + /// 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. + /// + /// # Errors + /// Will error if it is unable to set the user credentials. pub fn init(self) -> Result<()> { credentials::set_user_creds(self.username, self.password, self.registry)?; Ok(()) } /// Gets the current build's UUID + #[must_use] pub fn get_build_id() -> Uuid { *BUILD_ID } @@ -141,14 +173,22 @@ impl<'a> Strategy<'a> { INSPECT_STRATEGY.clone() } + /// Get the current environment credentials. + /// + /// # Errors + /// Will error if credentials don't exist. pub fn get_credentials() -> Result<&'static Credentials> { - credentials::get_credentials() + credentials::get() } /// 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. pub fn get_os_version(recipe: &Recipe) -> Result { trace!("get_os_version({recipe:#?})"); let image = format!("{}:{}", &recipe.base_image, &recipe.image_version); diff --git a/src/strategies/buildah_strategy.rs b/src/strategies/buildah_strategy.rs index ba543d1..c69e2dc 100644 --- a/src/strategies/buildah_strategy.rs +++ b/src/strategies/buildah_strategy.rs @@ -56,7 +56,7 @@ impl BuildStrategy for BuildahStrategy { fn login(&self) -> Result<()> { let (registry, username, password) = - credentials::get_credentials().map(|c| (&c.registry, &c.username, &c.password))?; + credentials::get().map(|c| (&c.registry, &c.username, &c.password))?; trace!("buildah login -u {username} -p [MASKED] {registry}"); let output = Command::new("buildah") diff --git a/src/strategies/credentials.rs b/src/strategies/credentials.rs index 43aaed2..123e4c9 100644 --- a/src/strategies/credentials.rs +++ b/src/strategies/credentials.rs @@ -1,7 +1,9 @@ use std::{env, sync::Mutex}; use anyhow::{anyhow, Result}; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + CI_REGISTRY, CI_REGISTRY_PASSWORD, CI_REGISTRY_USER, GITHUB_ACTIONS, GITHUB_ACTOR, GITHUB_TOKEN, +}; use once_cell::sync::Lazy; use typed_builder::TypedBuilder; @@ -44,9 +46,9 @@ static ENV_CREDENTIALS: Lazy> = Lazy::new(|| { let (username, password, registry) = { USER_CREDS.lock().map_or((None, None, None), |creds| { ( - creds.username.as_ref().map(|s| s.to_owned()), - creds.password.as_ref().map(|s| s.to_owned()), - creds.registry.as_ref().map(|s| s.to_owned()), + creds.username.as_ref().map(std::borrow::ToOwned::to_owned), + creds.password.as_ref().map(std::borrow::ToOwned::to_owned), + creds.registry.as_ref().map(std::borrow::ToOwned::to_owned), ) }) }; @@ -107,9 +109,9 @@ pub fn set_user_creds( let mut creds_lock = USER_CREDS .lock() .map_err(|e| anyhow!("Failed to set credentials: {e}"))?; - creds_lock.username = username.map(|s| s.to_owned()); - creds_lock.password = password.map(|s| s.to_owned()); - creds_lock.registry = registry.map(|s| s.to_owned()); + creds_lock.username = username.map(std::borrow::ToOwned::to_owned); + creds_lock.password = password.map(std::borrow::ToOwned::to_owned); + creds_lock.registry = registry.map(std::borrow::ToOwned::to_owned); drop(creds_lock); Ok(()) } @@ -118,7 +120,7 @@ pub fn set_user_creds( /// /// # Errors /// Will error if there aren't any credentials available. -pub fn get_credentials() -> Result<&'static Credentials> { +pub fn get() -> Result<&'static Credentials> { ENV_CREDENTIALS .as_ref() .ok_or_else(|| anyhow!("No credentials available")) diff --git a/src/strategies/docker_strategy.rs b/src/strategies/docker_strategy.rs index 3445c82..9b9d03f 100644 --- a/src/strategies/docker_strategy.rs +++ b/src/strategies/docker_strategy.rs @@ -4,7 +4,7 @@ use std::{ }; use anyhow::{bail, Result}; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{BB_BUILDKIT_CACHE_GHA, SKOPEO_IMAGE}; use log::{info, trace}; use crate::image_inspection::ImageInspection; @@ -83,7 +83,7 @@ impl BuildStrategy for DockerStrategy { fn login(&self) -> Result<()> { let (registry, username, password) = - credentials::get_credentials().map(|c| (&c.registry, &c.username, &c.password))?; + credentials::get().map(|c| (&c.registry, &c.username, &c.password))?; trace!("docker login -u {username} -p [MASKED] {registry}"); let output = Command::new("docker") diff --git a/src/strategies/podman_api_strategy.rs b/src/strategies/podman_api_strategy.rs index a986c2a..d20a390 100644 --- a/src/strategies/podman_api_strategy.rs +++ b/src/strategies/podman_api_strategy.rs @@ -1,6 +1,6 @@ use anyhow::Context; use anyhow::{bail, Result}; -use blue_build_utils::constants::*; +use blue_build_utils::constants::BUILD_ID_LABEL; use futures_util::StreamExt; use log::{debug, error}; use log::{info, trace}; @@ -104,7 +104,7 @@ impl BuildStrategy for PodmanApiStrategy { trace!("PodmanApiStrategy::push({image})"); let (username, password, registry) = - credentials::get_credentials().map(|c| (&c.username, &c.password, &c.registry))?; + credentials::get().map(|c| (&c.username, &c.password, &c.registry))?; trace!("Retrieved creds for user {username} on registry {registry}"); self.rt.block_on(async { diff --git a/src/strategies/podman_strategy.rs b/src/strategies/podman_strategy.rs index 9f09b0d..8853fc7 100644 --- a/src/strategies/podman_strategy.rs +++ b/src/strategies/podman_strategy.rs @@ -61,7 +61,7 @@ impl BuildStrategy for PodmanStrategy { fn login(&self) -> Result<()> { let (registry, username, password) = - credentials::get_credentials().map(|c| (&c.registry, &c.username, &c.password))?; + credentials::get().map(|c| (&c.registry, &c.username, &c.password))?; trace!("podman login -u {username} -p [MASKED] {registry}"); let output = Command::new("podman") diff --git a/template/src/lib.rs b/template/src/lib.rs index afaf478..52c8efa 100644 --- a/template/src/lib.rs +++ b/template/src/lib.rs @@ -1,7 +1,10 @@ use std::{borrow::Cow, env, fs, path::Path, process}; use blue_build_recipe::Recipe; -use blue_build_utils::constants::*; +use blue_build_utils::constants::{ + CI_PROJECT_NAME, CI_PROJECT_NAMESPACE, CI_REGISTRY, CI_SERVER_HOST, CI_SERVER_PROTOCOL, + COSIGN_PATH, GITHUB_REPOSITORY_OWNER, GITHUB_RESPOSITORY, GITHUB_SERVER_URL, +}; use log::{debug, error, trace}; use typed_builder::TypedBuilder; use uuid::Uuid; diff --git a/utils/src/constants.rs b/utils/src/constants.rs index f6fc916..85329d3 100644 --- a/utils/src/constants.rs +++ b/utils/src/constants.rs @@ -73,3 +73,5 @@ pub const NO_LABEL_ERROR_MESSAGE: &str = "It looks you have a Containerfile that has not been generated by BlueBuild. \ Running `build` will override your Containerfile and add an entry to the .gitignore. \ Do you want to continue?"; +pub const BUG_REPORT_WARNING_MESSAGE: &str = + "Please copy the above report and open an issue manually."; diff --git a/utils/src/lib.rs b/utils/src/lib.rs index fcfd210..0d04ce6 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,7 +1,7 @@ pub mod command_output; pub mod constants; -use std::{io::Write, path::PathBuf, process::Command, thread, time::Duration}; +use std::{ffi::OsStr, io::Write, path::PathBuf, process::Command, thread, time::Duration}; use anyhow::{anyhow, Result}; use format_serde_error::SerdeError; @@ -9,6 +9,10 @@ use log::{debug, trace}; pub use command_output::*; +/// Checks for the existance of a given command. +/// +/// # Errors +/// Will error if the command doesn't exist. pub fn check_command_exists(command: &str) -> Result<()> { trace!("check_command_exists({command})"); debug!("Checking if {command} exists"); @@ -29,9 +33,14 @@ pub fn check_command_exists(command: &str) -> Result<()> { } } -pub fn append_to_file(file_path: &str, content: &str) -> Result<()> { - trace!("append_to_file({file_path}, {content})"); - debug!("Appending {content} to {file_path}"); +/// Appends a string to a file. +/// +/// # Errors +/// Will error if it fails to append to a file. +pub fn append_to_file + AsRef>(file_path: &T, content: &str) -> Result<()> { + let file_path: PathBuf = file_path.into(); + trace!("append_to_file({}, {content})", file_path.display()); + debug!("Appending {content} to {}", file_path.display()); let mut file = std::fs::OpenOptions::new() .append(true) @@ -42,6 +51,8 @@ pub fn append_to_file(file_path: &str, content: &str) -> Result<()> { Ok(()) } +/// Creates a serde error for displaying the file +/// and where the error occurred. pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeError + '_ { |err: serde_yaml::Error| { let location = err.location(); @@ -57,10 +68,15 @@ pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeErro } } -pub fn retry(mut attempts: u8, delay: u64, f: F) -> anyhow::Result +/// Performs a retry on a given closure with a given nubmer of attempts and delay. +/// +/// # Errors +/// Will error when retries have been expended. +pub fn retry(attempts: u8, delay: u64, f: F) -> anyhow::Result where F: Fn() -> anyhow::Result, { + let mut attempts = attempts; loop { match f() { Ok(v) => return Ok(v),