From aa30244a56eb19850e0bf37a7a19c446147accdb Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 16 Nov 2023 21:18:42 +0000 Subject: [PATCH 1/6] Add --output-pattern=binary option, not wired up yet Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/cli.rs | 2 +- cargo-cyclonedx/src/config.rs | 1 + cargo-cyclonedx/src/generator.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cargo-cyclonedx/src/cli.rs b/cargo-cyclonedx/src/cli.rs index d6a91bbf..c1195dfd 100644 --- a/cargo-cyclonedx/src/cli.rs +++ b/cargo-cyclonedx/src/cli.rs @@ -79,7 +79,7 @@ Defaults to the host target, as printed by 'rustc -vV'" #[clap(long = "output-cdx")] pub output_cdx: bool, - /// Prefix patterns to use for the filename: bom, package + /// Prefix patterns to use for the filename: bom, package, binary #[clap( name = "output-pattern", long = "output-pattern", diff --git a/cargo-cyclonedx/src/config.rs b/cargo-cyclonedx/src/config.rs index 8b6119ad..03745cf1 100644 --- a/cargo-cyclonedx/src/config.rs +++ b/cargo-cyclonedx/src/config.rs @@ -164,6 +164,7 @@ pub enum Pattern { #[default] Bom, Package, + Binary, } impl FromStr for Pattern { diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index b1b1e0cb..e80d7933 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -631,6 +631,7 @@ impl GeneratedSbom { let prefix = match output_options.prefix { Prefix::Pattern(Pattern::Bom) => "bom".to_string(), Prefix::Pattern(Pattern::Package) => self.package_name.clone(), + Prefix::Pattern(Pattern::Binary) => todo!(), Prefix::Custom(c) => c.to_string(), }; From c1fc084dd7060571a53fa1c945745e3c04c61dbd Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 16 Nov 2023 21:50:29 +0000 Subject: [PATCH 2/6] Actually wire up the --output-pattern=binary option Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/generator.rs | 56 ++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index e80d7933..a4582594 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -53,9 +53,10 @@ use regex::Regex; use log::Level; use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::convert::TryFrom; use std::fs::File; -use std::io::BufWriter; +use std::io::Cursor; use std::io::Write; use std::path::PathBuf; use thiserror::Error; @@ -603,10 +604,9 @@ pub struct GeneratedSbom { impl GeneratedSbom { /// Writes SBOM to either a JSON or XML file in the same folder as `Cargo.toml` manifest pub fn write_to_file(self) -> Result<(), SbomWriterError> { - let path = self.manifest_path.with_file_name(self.filename()); - log::info!("Outputting {}", path.display()); - let file = File::create(path)?; - let mut writer = BufWriter::new(file); + let filenames = self.filenames(); + let serialized_sbom = Vec::new(); + let mut writer = Cursor::new(serialized_sbom); match self.sbom_config.format() { Format::Json => { self.bom @@ -619,20 +619,31 @@ impl GeneratedSbom { .map_err(SbomWriterError::XmlWriteError)?; } } + let serialized_sbom = writer.into_inner(); - // Flush the writer explicitly to catch and report any I/O errors - writer.flush()?; + for filename in filenames { + let path = self.manifest_path.with_file_name(filename); + log::info!("Outputting {}", path.display()); + let mut file = File::create(path)?; + file.write_all(&serialized_sbom)?; + } Ok(()) } - fn filename(&self) -> String { + fn filenames(&self) -> BTreeSet { let output_options = self.sbom_config.output_options(); - let prefix = match output_options.prefix { - Prefix::Pattern(Pattern::Bom) => "bom".to_string(), - Prefix::Pattern(Pattern::Package) => self.package_name.clone(), - Prefix::Pattern(Pattern::Binary) => todo!(), - Prefix::Custom(c) => c.to_string(), + let prefixes = match output_options.prefix { + Prefix::Pattern(Pattern::Bom) => vec!["bom".to_string()], + Prefix::Pattern(Pattern::Package) => vec![self.package_name.clone()], + Prefix::Custom(c) => vec![c.to_string()], + Prefix::Pattern(Pattern::Binary) => { + // different from the others in that we potentially output the same SBOM to multiple files + let meta = self.bom.metadata.as_ref().unwrap(); + let top_component = meta.component.as_ref().unwrap(); + let components = top_component.components.as_ref().unwrap(); + components.0.iter().map(|c| c.name.to_string()).collect() + } }; let platform_suffix = match output_options.platform_suffix { @@ -643,13 +654,18 @@ impl GeneratedSbom { } }; - format!( - "{}{}{}.{}", - prefix, - platform_suffix, - output_options.cdx_extension.extension(), - self.sbom_config.format() - ) + prefixes + .iter() + .map(|prefix| { + format!( + "{}{}{}.{}", + prefix, + platform_suffix, + output_options.cdx_extension.extension(), + self.sbom_config.format() + ) + }) + .collect() } } From fc475f86ab7fd8ecb1351cc72da7190ec644ff7a Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 16 Nov 2023 21:51:19 +0000 Subject: [PATCH 3/6] Add a comment Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/generator.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index a4582594..2c003bb4 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -638,7 +638,8 @@ impl GeneratedSbom { Prefix::Pattern(Pattern::Package) => vec![self.package_name.clone()], Prefix::Custom(c) => vec![c.to_string()], Prefix::Pattern(Pattern::Binary) => { - // different from the others in that we potentially output the same SBOM to multiple files + // Different from the others in that we potentially output the same SBOM to multiple files. + // We can safely `.unwrap()` here because we have just written these fields ourselves. let meta = self.bom.metadata.as_ref().unwrap(); let top_component = meta.component.as_ref().unwrap(); let components = top_component.components.as_ref().unwrap(); From 1eeef61575f59fbe1d01855a3a55297706e6b37c Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 16 Nov 2023 21:54:13 +0000 Subject: [PATCH 4/6] Implement conversion from str for Pattern::Binary Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cargo-cyclonedx/src/config.rs b/cargo-cyclonedx/src/config.rs index 03745cf1..cf98238a 100644 --- a/cargo-cyclonedx/src/config.rs +++ b/cargo-cyclonedx/src/config.rs @@ -174,6 +174,7 @@ impl FromStr for Pattern { match s { "bom" => Ok(Self::Bom), "package" => Ok(Self::Package), + "binary" => Ok(Self::Binary), _ => Err(format!("Expected bom or package, got `{}`", s)), } } From 7e1085016d801c6a271b91e9101bc93482cb49df Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 16 Nov 2023 22:23:24 +0000 Subject: [PATCH 5/6] Add a comment explaining the rationale Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/generator.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index 2c003bb4..427c8bfd 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -621,6 +621,10 @@ impl GeneratedSbom { } let serialized_sbom = writer.into_inner(); + // Why do we write the exact same SBOM into multiple files? + // Good question! And a long story! + // See https://github.com/CycloneDX/cyclonedx-rust-cargo/pull/563#issue-1997891622 + // for a detailed explanation of why this behavior was chosen. for filename in filenames { let path = self.manifest_path.with_file_name(filename); log::info!("Outputting {}", path.display()); From 69b606abe081d92e6d789810f410bf693ea0f2e0 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Sun, 26 Nov 2023 16:36:08 +0000 Subject: [PATCH 6/6] Better error message on invalid argument Signed-off-by: Sergey "Shnatsel" Davidoff --- cargo-cyclonedx/src/config.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cargo-cyclonedx/src/config.rs b/cargo-cyclonedx/src/config.rs index cf98238a..b61c36c4 100644 --- a/cargo-cyclonedx/src/config.rs +++ b/cargo-cyclonedx/src/config.rs @@ -175,7 +175,10 @@ impl FromStr for Pattern { "bom" => Ok(Self::Bom), "package" => Ok(Self::Package), "binary" => Ok(Self::Binary), - _ => Err(format!("Expected bom or package, got `{}`", s)), + _ => Err(format!( + "Expected 'bom', 'package' or 'binary', got `{}`", + s + )), } } }