From 26ddcea80009fbdcb56cde68d47bbcdd2fe67702 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 10:03:04 +0200 Subject: [PATCH 1/6] fix(boil)!: Don't override image manifest URIs in output The map used for stdout output and structured output via the CLI arg --write-image-manifest-uris automatically dropped URIs for the same image if multiple versions of that image were built at the same time. This is now fixed by mapping the image name to a list of tag sets instead of a single tag set. This is a breaking change, as the format of the structured output changes. This requires changes in the stackabletech/actions repo. --- rust/boil/src/cmd/build.rs | 13 ++++++++-- rust/boil/src/core/bakefile.rs | 43 +++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index b2c844e04..ad44e556e 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -50,7 +50,9 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro // TODO (@Techassi): Parse Dockerfile instead to build the target graph let bakefile = Bakefile::from_cli_args(&args, config).context(CreateBakefileSnafu)?; let image_manifest_uris = bakefile.image_manifest_uris(); - let count = image_manifest_uris.len(); + let count = image_manifest_uris + .iter() + .fold(0, |acc, (_, tags)| acc + tags.len()); // Write the image manifest URIs to file if requested if let Some(path) = args.write_image_manifest_uris { @@ -111,7 +113,14 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro let mut built_images = String::new(); for (name, tags) in image_manifest_uris { built_images.push_str(&format!("{name}:\n")); - built_images.push_str(&format!(" {tags}", tags = tags.join("\n "))); + built_images.push_str(&format!( + " {tags}", + tags = tags + .iter() + .map(|tags| tags.to_string()) + .collect::>() + .join("\n ") + )); built_images.push('\n'); } diff --git a/rust/boil/src/core/bakefile.rs b/rust/boil/src/core/bakefile.rs index 9d7588cd0..d10df60a5 100644 --- a/rust/boil/src/core/bakefile.rs +++ b/rust/boil/src/core/bakefile.rs @@ -1,6 +1,6 @@ use std::{ collections::{BTreeMap, btree_map::Entry}, - fmt::Debug, + fmt::{Debug, Display}, ops::{Deref, DerefMut}, path::PathBuf, }; @@ -334,16 +334,20 @@ impl Bakefile { } /// Returns all image manifest URIs for entry images. - pub fn image_manifest_uris(&self) -> BTreeMap<&str, &TagSet> { + pub fn image_manifest_uris(&self) -> BTreeMap<&str, Vec<&TagSet>> { self.targets .iter() // We only care about the entry targets, because those are the primary images boil // builds. .filter(|(target_name, _)| target_name.starts_with(ENTRY_TARGET_NAME_PREFIX)) // The image manifest URIs file only contains the image tags - .map(|(_, target)| (target.image_name.as_str(), &target.tags)) - // Group tags by image name and collect them into a map - .collect() + // Group tags by image name and collect them into a map of tag sets + .fold(BTreeMap::new(), |mut acc, (_, target)| { + acc.entry(target.image_name.as_str()) + .and_modify(|tags| tags.push(&target.tags)) + .or_insert(vec![&target.tags]); + acc + }) } /// Creates the common target, containing shared data, which will be inherited by other targets. @@ -796,6 +800,21 @@ impl Default for TagSet { } } +impl Display for TagSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.others.is_empty() { + write!(f, "{canonical}", canonical = self.canonical) + } else { + write!( + f, + "{canonical} ({others})", + canonical = self.canonical, + others = self.others.join(",") + ) + } + } +} + impl TagSet { /// Creates and returns a new [`TagSet`] which only contains the canonical tag. pub fn new(canonical: String) -> Self { @@ -824,20 +843,6 @@ impl TagSet { } } - /// Joins the tags using `separator` into a [`String`]. - pub fn join(&self, separator: &str) -> String { - let mut joined = String::new(); - - joined.push_str(&self.canonical); - - if !self.others.is_empty() { - joined.push_str(separator); - } - - joined.push_str(&self.others.join(separator)); - joined - } - /// Returns if the [`TagSet`] is empty. /// /// Used for skipping serialization in the common target of the [`Bakefile`]. From 73aa618051682b2df638378997f02e6a39720499 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 10:17:47 +0200 Subject: [PATCH 2/6] chore(boil): Rename image count variable --- rust/boil/src/cmd/build.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index ad44e556e..ebdd224bb 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -50,7 +50,7 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro // TODO (@Techassi): Parse Dockerfile instead to build the target graph let bakefile = Bakefile::from_cli_args(&args, config).context(CreateBakefileSnafu)?; let image_manifest_uris = bakefile.image_manifest_uris(); - let count = image_manifest_uris + let image_count = image_manifest_uris .iter() .fold(0, |acc, (_, tags)| acc + tags.len()); @@ -125,8 +125,8 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro } print!( - "Successfully built {count} image{plural}:\n{built_images}", - plural = if count > 1 { "s" } else { "" }, + "Successfully built {image_count} image{plural}:\n{built_images}", + plural = if image_count > 1 { "s" } else { "" }, ); Ok(()) From 390ec8137ccbc091214bd9dd653e3b0663812596 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 10:31:47 +0200 Subject: [PATCH 3/6] chore(boil): Add number of tags to stdout output --- rust/boil/src/cmd/build.rs | 18 ++++++++++++++---- rust/boil/src/core/bakefile.rs | 8 ++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index ebdd224bb..b564cb1a3 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -111,16 +111,26 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro // Take care of formatting output let mut built_images = String::new(); - for (name, tags) in image_manifest_uris { - built_images.push_str(&format!("{name}:\n")); + for (name, tag_sets) in image_manifest_uris { + let tag_count = tag_sets.iter().fold(0, |acc, tag_set| acc + tag_set.len()); + + // Add the image name line first. It also includes the number to tags for that particular + // image. + built_images.push_str(&format!( + "{name} ({tag_count} tag{plural}):\n", + plural = if tag_count > 1 { "s" } else { "" } + )); + + // Add an indented list of tags, one tag set per line. built_images.push_str(&format!( " {tags}", - tags = tags + tags = tag_sets .iter() - .map(|tags| tags.to_string()) + .map(|tag_set| tag_set.to_string()) .collect::>() .join("\n ") )); + built_images.push('\n'); } diff --git a/rust/boil/src/core/bakefile.rs b/rust/boil/src/core/bakefile.rs index d10df60a5..a60efa0e1 100644 --- a/rust/boil/src/core/bakefile.rs +++ b/rust/boil/src/core/bakefile.rs @@ -832,6 +832,14 @@ impl TagSet { tag_set } + pub fn len(&self) -> usize { + if self.is_empty { + 0 + } else { + self.others.len() + 1 + } + } + /// Create an empty [`TagSet`]. /// /// See the `is_empty` field for more details on this. From 5acb07a22bf9667941c98626d3bbdd027f819d63 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 11:12:57 +0200 Subject: [PATCH 4/6] chore: Apply suggestion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sönke Liebau --- rust/boil/src/cmd/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index b564cb1a3..a87e39c24 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -114,7 +114,7 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro for (name, tag_sets) in image_manifest_uris { let tag_count = tag_sets.iter().fold(0, |acc, tag_set| acc + tag_set.len()); - // Add the image name line first. It also includes the number to tags for that particular + // Add the image name line first. It also includes the number of tags for that particular // image. built_images.push_str(&format!( "{name} ({tag_count} tag{plural}):\n", From ae3d50861bd2a929771e0257df2f8a8d65f153e6 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 11:17:05 +0200 Subject: [PATCH 5/6] chore(boil): Add doc comment for TagSet::len --- rust/boil/src/core/bakefile.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/boil/src/core/bakefile.rs b/rust/boil/src/core/bakefile.rs index a60efa0e1..a47b59182 100644 --- a/rust/boil/src/core/bakefile.rs +++ b/rust/boil/src/core/bakefile.rs @@ -832,6 +832,10 @@ impl TagSet { tag_set } + /// Returns the length of the [`TagSet`] (number of tags). + /// + /// If the [`TagSet`] is marked as empty, it will return 0. Otherwise, the count is the sum of + /// the length of other tags and 1 (one) for the canonical tag. pub fn len(&self) -> usize { if self.is_empty { 0 From 74e68e196d884e4d0e3b862968b4f8a6a149b163 Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Aug 2026 11:17:46 +0200 Subject: [PATCH 6/6] chore(boil): Rename variable in fold combinator --- rust/boil/src/cmd/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/boil/src/cmd/build.rs b/rust/boil/src/cmd/build.rs index a87e39c24..4b2e082a7 100644 --- a/rust/boil/src/cmd/build.rs +++ b/rust/boil/src/cmd/build.rs @@ -52,7 +52,7 @@ pub fn run_command(args: Box, config: Config) -> Result<(), Erro let image_manifest_uris = bakefile.image_manifest_uris(); let image_count = image_manifest_uris .iter() - .fold(0, |acc, (_, tags)| acc + tags.len()); + .fold(0, |acc, (_, tag_sets)| acc + tag_sets.len()); // Write the image manifest URIs to file if requested if let Some(path) = args.write_image_manifest_uris {