Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion process/drivers/opts/rechunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct RechunkOpts<'scope> {
pub platform: &'scope [Platform],
pub version: &'scope str,
pub name: &'scope str,
pub description: &'scope str,
pub description: Option<&'scope str>,
pub base_digest: &'scope str,
pub base_image: &'scope Reference,
pub repo: &'scope str,
Expand Down
22 changes: 15 additions & 7 deletions recipe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ trait RecipeSetters: RecipeGetters {

pub trait RecipeGetters {
fn get_name(&self) -> &str;
fn get_description(&self) -> &str;
fn get_description(&self) -> Option<&str>;
fn get_modules(&self) -> &[Module];
fn get_stages(&self) -> &[Stage];
fn get_labels(&self) -> HashMap<&String, &String>;
fn get_labels(&self) -> HashMap<&str, &str>;
fn get_alt_tags(&self) -> Option<&[Tag]>;
fn get_platforms(&self) -> &[Platform];
fn get_base_image(&self) -> Cow<'_, str>;
Expand All @@ -85,21 +85,21 @@ pub trait RecipeGetters {
&self,
default_labels: &BTreeMap<String, String>,
) -> BTreeMap<String, String> {
let mut labels = default_labels.iter().chain(self.get_labels()).fold(
let mut labels = default_labels.iter().map(|(key, value)| (&**key, &**value)).chain(self.get_labels()).fold(
BTreeMap::new(),
|mut acc, (k, v)| {
if let Some(existing_value) = acc.get(k) {
warn!("Found conflicting values for label: {k}, contains: {existing_value}, overwritten by: {v}");
}
acc.insert(k.clone(), v.clone());
acc.insert(k.to_owned(), v.to_owned());
acc
},
);

if !labels.contains_key("io.artifacthub.package.readme-url") {
// adding this if not included in the custom labeling to maintain backwards compatibility since this was hardcoded into the old template
labels.insert(
"io.artifacthub.package.readme-url".to_string(),
"io.artifacthub.package.readme-url".into(),
"https://raw.githubusercontent.com/blue-build/cli/main/README.md".into(),
);
}
Expand Down Expand Up @@ -207,6 +207,14 @@ impl Recipe {
inner(path.as_ref())
}

#[must_use]
pub fn upgrade(self) -> Self {
match self {
Self::V1(recipe) => Self::V2(Box::new(RecipeV2::from(*recipe))),
me @ Self::V2(_) => me,
}
}

fn version_number(&self) -> Number {
match self {
Self::V1(_) => 1,
Expand Down Expand Up @@ -269,7 +277,7 @@ impl RecipeGetters for Recipe {
impl_recipe!(self, get_name())
}

fn get_description(&self) -> &str {
fn get_description(&self) -> Option<&str> {
impl_recipe!(self, get_description())
}

Expand All @@ -281,7 +289,7 @@ impl RecipeGetters for Recipe {
impl_recipe!(self, get_stages())
}

fn get_labels(&self) -> HashMap<&String, &String> {
fn get_labels(&self) -> HashMap<&str, &str> {
impl_recipe!(self, get_labels())
}

Expand Down
10 changes: 5 additions & 5 deletions recipe/src/recipe_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ impl RecipeGetters for RecipeV1 {
&self.name
}

fn get_description(&self) -> &str {
&self.description
fn get_description(&self) -> Option<&str> {
Some(&self.description)
}

fn get_base_image(&self) -> Cow<'_, str> {
Cow::Borrowed(&**self.base_image)
Cow::Borrowed(&self.base_image)
}

fn base_image_ref(&self) -> Result<Reference> {
Expand All @@ -118,11 +118,11 @@ impl RecipeGetters for RecipeV1 {
.with_context(|| format!("Unable to parse base image {base_image}"))
}

fn get_labels(&self) -> HashMap<&String, &String> {
fn get_labels(&self) -> HashMap<&str, &str> {
self.labels
.iter()
.flatten()
.map(|(key, value)| (key, &**value))
.map(|(key, value)| (&**key, &**value))
.collect()
}

Expand Down
140 changes: 114 additions & 26 deletions recipe/src/recipe_v2.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,50 @@
use std::{borrow::Cow, collections::HashMap};
use std::{borrow::Cow, collections::HashMap, ops::Deref};

use blue_build_utils::{
constants::BLUE_BUILD_DEFAULT_IMAGE, container::Tag, env_str::EnvString, platform::Platform,
};
use bon::Builder;
use miette::{Context, IntoDiagnostic};
use oci_client::Reference;
use serde::{Deserialize, Serialize};
use structstruck::strike;

use crate::{Module, RecipeGetters, RecipeSetters, Stage};
use crate::{Module, RecipeGetters, RecipeSetters, RecipeV1, Stage};

use super::{MaybeVersion, ModuleExt, StagesExt};

#[derive(Debug, Clone, Serialize)]
pub struct RecipeV2BaseImageStr(EnvString);

impl From<Reference> for RecipeV2BaseImageStr {
fn from(value: Reference) -> Self {
Self(EnvString::from(value.to_string()))
}
}

impl<'de> Deserialize<'de> for RecipeV2BaseImageStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let image = EnvString::deserialize(deserializer)?;

if let Err(e) = image.parse::<Reference>() {
return Err(serde::de::Error::custom(e));
}

Ok(Self(image))
}
}

impl Deref for RecipeV2BaseImageStr {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.0
}
}

strike! {
/// The build recipe.
///
Expand All @@ -35,7 +68,7 @@ strike! {
#![serde(untagged)]

/// String representation of an image ref.
Str(Reference),
Str(RecipeV2BaseImageStr),

/// Object representation of an image ref.
Obj {
Expand Down Expand Up @@ -69,6 +102,7 @@ strike! {
/// This will validate the image before building with it.
///
/// URLs are supported. Paths are relative to the root of the project.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub public_key: Option<EnvString>,
},

Expand All @@ -78,7 +112,12 @@ strike! {
pub name: EnvString,

/// The image description. Published to GHCR in the image metadata.
pub description: EnvString,
pub description: Option<EnvString>,

/// Allows setting custom tags on the recipe’s final image.
/// Adding tags to this property will override the `latest` and timestamp tags.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,

/// A collection of custom labels that will be applied to the image.
///
Expand All @@ -90,11 +129,6 @@ strike! {
/// Specifications for the image that modifies how it is built and published.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub spec: Option<pub struct {
/// Allows setting custom tags on the recipe’s final image.
/// Adding tags to this property will override the `latest` and timestamp tags.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,

/// Specify a list of the platforms to build for your image.
/// The resulting images will be added to a manifest list that
/// allows your host’s container runtime to pull the correct
Expand Down Expand Up @@ -159,13 +193,63 @@ strike! {
}
}

impl From<RecipeV1> for RecipeV2 {
fn from(value: RecipeV1) -> Self {
Self {
base: RecipeV2Base {
image: RecipeV2BaseImage::Str(RecipeV2BaseImageStr(EnvString::from(format!(
"{}:{}",
value.base_image.unexpanded(),
value.image_version.unexpanded(),
)))),
public_key: None,
},
metadata: RecipeV2Metadata {
name: value.name,
description: Some(value.description),
tags: value.alt_tags.unwrap_or_default(),
labels: value.labels,
},
spec: {
let has_versions = value.blue_build_tag.is_some()
|| value.cosign_version.is_some()
|| value.nushell_version.is_some();
let tool_versions = has_versions.then_some(RecipeV2SpecToolVersions {
bluebuild: value.blue_build_tag,
nushell: match value.nushell_version {
None | Some(MaybeVersion::None) => None,
Some(MaybeVersion::VersionOrBranch(tag)) => Some(tag),
},
cosign: match value.cosign_version {
None | Some(MaybeVersion::None) => None,
Some(MaybeVersion::VersionOrBranch(tag)) => Some(tag),
},
});
match (value.platforms, has_versions) {
(None, false) => None,
(Some(platforms), false) => Some(RecipeV2Spec {
platforms,
tool_versions: None,
}),
(Some(platforms), true) => Some(RecipeV2Spec {
platforms,
tool_versions,
}),
(None, true) => Some(RecipeV2Spec {
platforms: Vec::default(),
tool_versions,
}),
}
},
stages_ext: value.stages_ext,
modules_ext: value.modules_ext,
}
}
}

impl Default for RecipeV2BaseImage {
fn default() -> Self {
Self::Str(
BLUE_BUILD_DEFAULT_IMAGE
.try_into()
.expect("Should be a valid image ref"),
)
Self::Str(RecipeV2BaseImageStr(BLUE_BUILD_DEFAULT_IMAGE.into()))
}
}

Expand All @@ -182,22 +266,21 @@ impl RecipeGetters for RecipeV2 {
&self.metadata.name
}

fn get_description(&self) -> &str {
&self.metadata.description
fn get_description(&self) -> Option<&str> {
self.metadata.description.as_deref()
}

fn get_labels(&self) -> HashMap<&String, &String> {
fn get_labels(&self) -> HashMap<&str, &str> {
self.metadata
.labels
.iter()
.flatten()
.map(|(key, value)| (key, &**value))
.map(|(key, value)| (&**key, &**value))
.collect()
}

fn get_alt_tags(&self) -> Option<&[Tag]> {
let spec = self.spec.as_ref()?;
match &spec.tags[..] {
match &self.metadata.tags[..] {
[] => None,
tags => Some(tags),
}
Expand All @@ -209,11 +292,13 @@ impl RecipeGetters for RecipeV2 {

fn get_base_image(&self) -> Cow<'_, str> {
match &self.base.image {
RecipeV2BaseImage::Str(image) => Cow::Owned(format!(
"{}/{}",
image.resolve_registry(),
image.repository()
)),
RecipeV2BaseImage::Str(image) => Cow::Borrowed(
image
.split_once(':') // Split at tag start
.or_else(|| image.split_once('@')) // or digest start
.unwrap_or((image, "")) // or the image without a tag
.0,
),
RecipeV2BaseImage::Obj {
registry,
repository,
Expand Down Expand Up @@ -256,7 +341,10 @@ impl RecipeGetters for RecipeV2 {

fn base_image_ref(&self) -> miette::Result<Reference> {
Ok(match &self.base.image {
RecipeV2BaseImage::Str(image) => image.clone(),
RecipeV2BaseImage::Str(RecipeV2BaseImageStr(image)) => image
.parse()
.into_diagnostic()
.wrap_err_with(|| format!("Failed to parse base image ref {image}"))?,
RecipeV2BaseImage::Obj {
registry,
repository,
Expand Down
1 change: 1 addition & 0 deletions src/bin/bluebuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ fn main() {
CommandArgs::Prune(mut command) => command.run(),
CommandArgs::BugReport(mut command) => command.run(),
CommandArgs::Completions(mut command) => command.run(),
CommandArgs::Recipe(mut command) => command.run(),
});
}
3 changes: 3 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod generate_iso;
pub mod init;
pub mod login;
pub mod prune;
pub mod recipe;
pub mod switch;
pub mod validate;

Expand Down Expand Up @@ -114,6 +115,8 @@ pub enum CommandArgs {

/// Generate shell completions for your shell to stdout
Completions(completions::CompletionsCommand),

Recipe(recipe::RecipeCommand),
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ impl BuildCommand {
.base_digest(base_digest)
.repo(&Driver::get_repo_url()?)
.name(recipe.get_name())
.description(recipe.get_description())
.maybe_description(recipe.get_description())
.base_image(&base_image)
.maybe_tempdir(self.tempdir.as_deref())
.clear_plan(self.rechunk_clear_plan)
Expand Down
Loading
Loading