From afe4d3637b2b71b20babd1243f83dd1fa2ec45bc Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Fri, 2 Feb 2024 18:35:07 -0500 Subject: [PATCH 1/2] fix: Create proper image tags --- src/commands/build.rs | 12 +++--- src/module_recipe.rs | 87 ++++++++++++++++++++++++++++++++++------- templates/Containerfile | 1 - 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/commands/build.rs b/src/commands/build.rs index c2885466..120f9fef 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -190,7 +190,7 @@ impl BuildCommand { trace!("recipe: {recipe:#?}"); // Get values for image - let tags = recipe.generate_tags(); + let tags = recipe.generate_tags()?; let image_name = self.generate_full_image_name(&recipe)?; let first_image_name = if self.archive.is_some() { image_name.to_string() @@ -271,7 +271,7 @@ impl BuildCommand { trace!("BuildCommand::build_image()"); let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(&self.recipe)?.as_str())?; - let tags = recipe.generate_tags(); + let tags = recipe.generate_tags()?; let image_name = self.generate_full_image_name(&recipe)?; @@ -590,7 +590,8 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> { } } (_, _, _, _, _, _, Ok(github_event_name), Ok(github_ref_name), Ok(_)) - if github_event_name != "pull_request" && github_ref_name == "live" => + if github_event_name != "pull_request" + && (github_ref_name == "live" || github_ref_name == "main") => { trace!("GITHUB_EVENT_NAME={github_event_name}, GITHUB_REF_NAME={github_ref_name}"); @@ -663,8 +664,9 @@ fn check_cosign_files() -> Result<()> { env::var("GITHUB_REF_NAME").ok(), env::var("COSIGN_PRIVATE_KEY").ok(), ) { - (Some(github_event_name), Some(github_ref), Some(_)) - if github_event_name != "pull_request" && github_ref == "live" => + (Some(github_event_name), Some(github_ref_name), Some(_)) + if github_event_name != "pull_request" + && (github_ref_name == "live" || github_ref_name == "main") => { env::set_var("COSIGN_PASSWORD", ""); env::set_var("COSIGN_YES", "true"); diff --git a/src/module_recipe.rs b/src/module_recipe.rs index 23089254..130380f5 100644 --- a/src/module_recipe.rs +++ b/src/module_recipe.rs @@ -1,12 +1,22 @@ -use std::{borrow::Cow, env, fs, path::Path, process}; - +use std::{ + borrow::Cow, + collections::HashMap, + env, fs, + path::Path, + process::{self, Command}, +}; + +use anyhow::{bail, Result}; use chrono::Local; use indexmap::IndexMap; -use log::{debug, error, trace, warn}; +use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; use serde_yaml::Value; use typed_builder::TypedBuilder; +use crate::ops::check_command_exists; + #[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] pub struct Recipe<'a> { #[builder(setter(into))] @@ -36,13 +46,12 @@ pub struct Recipe<'a> { } impl<'a> Recipe<'a> { - #[must_use] - pub fn generate_tags(&self) -> Vec { + pub fn generate_tags(&self) -> Result> { trace!("Recipe::generate_tags()"); trace!("Generating image tags for {}", &self.name); let mut tags: Vec = Vec::new(); - let image_version = &self.image_version; + let image_version = self.get_os_version()?; let timestamp = Local::now().format("%Y%m%d").to_string(); if let (Ok(commit_branch), Ok(default_branch), Ok(commit_sha), Ok(pipeline_source)) = ( @@ -65,11 +74,12 @@ impl<'a> Recipe<'a> { if default_branch == commit_branch { debug!("Running on the default branch"); tags.push(image_version.to_string()); - tags.push(format!("{image_version}-{timestamp}")); + tags.push(format!("{timestamp}-{image_version}")); + tags.push("latest".into()); tags.push(timestamp); } else { debug!("Running on branch {commit_branch}"); - tags.push(format!("{commit_branch}-{image_version}")); + tags.push(format!("br-{commit_branch}-{image_version}")); } tags.push(format!("{commit_sha}-{image_version}")); @@ -93,27 +103,29 @@ impl<'a> Recipe<'a> { if github_event_name == "pull_request" { debug!("Running in a PR"); tags.push(format!("pr-{github_event_number}-{image_version}")); - } else if github_ref_name == "live" { + } else if github_ref_name == "live" || github_ref_name == "main" { tags.push(image_version.to_string()); - tags.push(format!("{image_version}-{timestamp}")); - tags.push("latest".to_string()); + tags.push(format!("{timestamp}-{image_version}")); + tags.push("latest".into()); + tags.push(timestamp); } else { tags.push(format!("br-{github_ref_name}-{image_version}")); } tags.push(format!("{short_sha}-{image_version}")); } else { warn!("Running locally"); - tags.push(format!("{image_version}-local")); + tags.push(format!("local-{image_version}")); } debug!("Finished generating tags!"); debug!("Tags: {tags:#?}"); - tags + + Ok(tags) } /// # Parse a recipe file /// # /// # Errors - pub fn parse>(path: &P) -> anyhow::Result { + pub fn parse>(path: &P) -> Result { let file_path = if Path::new(path.as_ref()).is_absolute() { path.as_ref().to_path_buf() } else { @@ -136,6 +148,33 @@ impl<'a> Recipe<'a> { process::exit(1); }) } + + fn get_os_version(&self) -> Result { + trace!("Recipe::get_os_version()"); + check_command_exists("skopeo")?; + + let base_image = self.base_image.as_ref(); + let image_version = self.image_version.as_ref(); + + info!("Retrieving information from {base_image}:{image_version}, this will take a bit"); + + let output = Command::new("skopeo") + .arg("inspect") + .arg(format!("docker://{base_image}:{image_version}")) + .output()?; + + if !output.status.success() { + bail!("Failed to get image information for {base_image}:{image_version}"); + } + + let inspection: ImageInspection = + serde_json::from_str(String::from_utf8(output.stdout)?.as_str())?; + + Ok(inspection.get_version().unwrap_or_else(|| { + warn!("Version label does not exist on image, using version in recipe"); + image_version.to_string() + })) + } } #[derive(Default, Serialize, Clone, Deserialize, Debug, TypedBuilder)] @@ -158,3 +197,23 @@ pub struct Module { #[builder(default, setter(into))] pub config: IndexMap, } + +#[derive(Deserialize, Debug, Clone)] +struct ImageInspection { + #[serde(alias = "Labels")] + labels: HashMap, +} + +impl ImageInspection { + pub fn get_version(&self) -> Option { + Some( + self.labels + .get("org.opencontainers.image.version")? + .as_str() + .map(|v| v.to_string())? + .split('.') + .take(1) + .collect(), + ) + } +} diff --git a/templates/Containerfile b/templates/Containerfile index 66bba6cd..23f26860 100644 --- a/templates/Containerfile +++ b/templates/Containerfile @@ -1,7 +1,6 @@ FROM {{ recipe.base_image }}:{{ recipe.image_version }} LABEL org.opencontainers.image.title="{{ recipe.name }}" -LABEL org.opencontainers.image.version="{{ recipe.image_version }}" LABEL org.opencontainers.image.description="{{ recipe.description }}" LABEL io.artifacthub.package.readme-url=https://raw.githubusercontent.com/ublue-os/startingpoint/main/README.md LABEL io.artifacthub.package.logo-url=https://avatars.githubusercontent.com/u/120078124?s=200&v=4 From 670b333c135762f19fa47d81f23fd2e4a3a66840 Mon Sep 17 00:00:00 2001 From: Gerald Pinder Date: Fri, 2 Feb 2024 19:43:28 -0500 Subject: [PATCH 2/2] Use recipe image version as fallback if there are errors --- src/commands/build.rs | 4 ++-- src/module_recipe.rs | 47 +++++++++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/commands/build.rs b/src/commands/build.rs index 120f9fef..e5ff15a4 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -190,7 +190,7 @@ impl BuildCommand { trace!("recipe: {recipe:#?}"); // Get values for image - let tags = recipe.generate_tags()?; + let tags = recipe.generate_tags(); let image_name = self.generate_full_image_name(&recipe)?; let first_image_name = if self.archive.is_some() { image_name.to_string() @@ -271,7 +271,7 @@ impl BuildCommand { trace!("BuildCommand::build_image()"); let recipe: Recipe = serde_yaml::from_str(fs::read_to_string(&self.recipe)?.as_str())?; - let tags = recipe.generate_tags()?; + let tags = recipe.generate_tags(); let image_name = self.generate_full_image_name(&recipe)?; diff --git a/src/module_recipe.rs b/src/module_recipe.rs index 130380f5..970f6a7e 100644 --- a/src/module_recipe.rs +++ b/src/module_recipe.rs @@ -6,7 +6,7 @@ use std::{ process::{self, Command}, }; -use anyhow::{bail, Result}; +use anyhow::Result; use chrono::Local; use indexmap::IndexMap; use log::{debug, error, info, trace, warn}; @@ -46,12 +46,12 @@ pub struct Recipe<'a> { } impl<'a> Recipe<'a> { - pub fn generate_tags(&self) -> Result> { + pub fn generate_tags(&self) -> Vec { trace!("Recipe::generate_tags()"); trace!("Generating image tags for {}", &self.name); let mut tags: Vec = Vec::new(); - let image_version = self.get_os_version()?; + let image_version = self.get_os_version(); let timestamp = Local::now().format("%Y%m%d").to_string(); if let (Ok(commit_branch), Ok(default_branch), Ok(commit_sha), Ok(pipeline_source)) = ( @@ -119,7 +119,7 @@ impl<'a> Recipe<'a> { debug!("Finished generating tags!"); debug!("Tags: {tags:#?}"); - Ok(tags) + tags } /// # Parse a recipe file @@ -149,31 +149,52 @@ impl<'a> Recipe<'a> { }) } - fn get_os_version(&self) -> Result { + fn get_os_version(&self) -> String { trace!("Recipe::get_os_version()"); - check_command_exists("skopeo")?; + + if check_command_exists("skopeo").is_err() { + warn!("The 'skopeo' command doesn't exist, falling back to version defined in recipe"); + return self.image_version.to_string(); + } let base_image = self.base_image.as_ref(); let image_version = self.image_version.as_ref(); info!("Retrieving information from {base_image}:{image_version}, this will take a bit"); - let output = Command::new("skopeo") + let output = match Command::new("skopeo") .arg("inspect") .arg(format!("docker://{base_image}:{image_version}")) - .output()?; + .output() + { + Err(_) => { + warn!( + "Issue running the 'skopeo' command, falling back to version defined in recipe" + ); + return self.image_version.to_string(); + } + Ok(output) => output, + }; if !output.status.success() { - bail!("Failed to get image information for {base_image}:{image_version}"); + warn!("Failed to get image information for {base_image}:{image_version}, falling back to version defined in recipe"); + return self.image_version.to_string(); } - let inspection: ImageInspection = - serde_json::from_str(String::from_utf8(output.stdout)?.as_str())?; + let inspection: ImageInspection = match serde_json::from_str( + String::from_utf8_lossy(&output.stdout).as_ref(), + ) { + Err(_) => { + warn!("Issue deserializing 'skopeo' output, falling back to version defined in recipe"); + return self.image_version.to_string(); + } + Ok(inspection) => inspection, + }; - Ok(inspection.get_version().unwrap_or_else(|| { + inspection.get_version().unwrap_or_else(|| { warn!("Version label does not exist on image, using version in recipe"); image_version.to_string() - })) + }) } }