diff --git a/Cargo.toml b/Cargo.toml index 230446e..bcb5e73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ edition = "2024" license = "Apache-2.0" rust-version = "1.87.0" -version = "0.14.2" +version = "0.15.0" [workspace.dependencies] anyhow = "1" @@ -73,10 +73,10 @@ walkdir = "2.4" # internal dependencies -csaf-walker = { version = "0.14.2", path = "csaf", default-features = false } -sbom-walker = { version = "0.14.2", path = "sbom", default-features = false } -walker-common = { version = "0.14.2", path = "common" } -walker-extras = { version = "0.14.2", path = "extras" } +csaf-walker = { version = "0.15.0", path = "csaf", default-features = false } +sbom-walker = { version = "0.15.0", path = "sbom", default-features = false } +walker-common = { version = "0.15.0", path = "common" } +walker-extras = { version = "0.15.0", path = "extras" } [workspace.metadata.release] tag = false diff --git a/common/Cargo.toml b/common/Cargo.toml index 0b05272..732c379 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -16,8 +16,6 @@ version.workspace = true # normal anyhow = { workspace = true } async-trait = { workspace = true } -aws-config = { workspace = true } -aws-sdk-s3 = { workspace = true } backon = { workspace = true } base64 = { workspace = true } bytes = { workspace = true } @@ -49,6 +47,8 @@ url = { workspace = true } walkdir = { workspace = true } # optional +aws-config = { workspace = true, optional = true } +aws-sdk-s3 = { workspace = true, optional = true } bzip2 = { workspace = true, optional = true } bzip2-rs = { workspace = true, optional = true, features = ["rustc_1_51"] } clap = { workspace = true, features = ["derive", "env"], optional = true } @@ -60,6 +60,7 @@ sequoia-openpgp = { workspace = true, optional = true } [features] default = ["bzip2"] openpgp = ["sequoia-openpgp"] +s3 = ["dep:aws-config", "dep:aws-sdk-s3"] # deprecated cli = ["clap", "env_logger"] diff --git a/common/src/compression/detecting.rs b/common/src/compression/detecting.rs index 371478c..4ec9d83 100644 --- a/common/src/compression/detecting.rs +++ b/common/src/compression/detecting.rs @@ -117,13 +117,13 @@ pub struct Detector<'a> { impl<'a> Detector<'a> { /// Detect and decompress in a single step. - pub fn decompress(&'a self, data: Bytes) -> Result> { + pub fn decompress(&self, data: Bytes) -> Result> { self.decompress_with(data, &Default::default()) } /// Detect and decompress in a single step. pub fn decompress_with( - &'a self, + &self, data: Bytes, opts: &DecompressionOptions, ) -> Result> { @@ -131,7 +131,7 @@ impl<'a> Detector<'a> { Ok(compression.decompress_with(data, opts)?) } - pub fn detect(&'a self, #[allow(unused)] data: &[u8]) -> Result> { + pub fn detect(&self, #[allow(unused)] data: &[u8]) -> Result> { // detect by file name extension if let Some(file_name) = self.file_name { @@ -218,4 +218,13 @@ mod test { fn by_name_gzip() { assert_eq!(detect("foo.bar.gz"), Compression::Gzip); } + + #[test] + fn default() { + // we're not interested in running this, just ensuring we can use the Default ergonomically + let _result = Detector::default().decompress(Bytes::from_static(b"foo")); + + let detector = Detector::default(); + let _result = detector.decompress(Bytes::from_static(b"foo")); + } } diff --git a/common/src/scoop/source.rs b/common/src/scoop/source.rs index 2d96e6a..9486df2 100644 --- a/common/src/scoop/source.rs +++ b/common/src/scoop/source.rs @@ -1,10 +1,4 @@ -use crate::USER_AGENT; use anyhow::bail; -use aws_config::{BehaviorVersion, Region, meta::region::RegionProviderChain}; -use aws_sdk_s3::{ - Client, - config::{AppName, Credentials}, -}; use bytes::Bytes; use std::{ borrow::Cow, @@ -12,10 +6,20 @@ use std::{ }; use url::Url; +#[cfg(feature = "s3")] +use aws_config::{BehaviorVersion, Region, meta::region::RegionProviderChain}; +#[cfg(feature = "s3")] +use aws_sdk_s3::{ + Client, + config::{AppName, Credentials}, +}; + #[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum Source { Path(PathBuf), Http(Url), + #[cfg(feature = "s3")] S3(S3), } @@ -23,15 +27,20 @@ impl TryFrom<&str> for Source { type Error = anyhow::Error; fn try_from(value: &str) -> Result { - Ok( - if value.starts_with("http://") || value.starts_with("https://") { - Self::Http(Url::parse(value)?) - } else if value.starts_with("s3://") { - Self::S3(S3::try_from(value)?) - } else { - Self::Path(value.into()) - }, - ) + if value.starts_with("http://") || value.starts_with("https://") { + return Ok(Self::Http(Url::parse(value)?)); + } + + #[cfg(feature = "s3")] + if value.starts_with("s3://") { + return Ok(Self::S3(S3::try_from(value)?)); + } + #[cfg(not(feature = "s3"))] + if value.starts_with("s3://") { + bail!("S3 URLs are not supported"); + } + + Ok(Self::Path(value.into())) } } @@ -42,6 +51,7 @@ impl Source { .into_iter() .map(Self::Path) .collect()), + #[cfg(feature = "s3")] Self::S3(s3) if s3.key.is_none() => Ok(Self::discover_s3(s3) .await? .into_iter() @@ -77,6 +87,7 @@ impl Source { } } + #[cfg(feature = "s3")] async fn discover_s3(s3: S3) -> anyhow::Result> { let client = s3.client().await?; @@ -110,6 +121,7 @@ impl Source { match self { Self::Path(path) => path.to_string_lossy(), Self::Http(url) => url.as_str().into(), + #[cfg(feature = "s3")] Self::S3(s3) => format!( "s3://{}/{}/{}", s3.region, @@ -131,6 +143,7 @@ impl Source { .bytes() .await? } + #[cfg(feature = "s3")] Self::S3(s3) => { let client = s3.client(); client @@ -163,6 +176,7 @@ impl Source { .send() .await?; } + #[cfg(feature = "s3")] Self::S3(s3) => { // delete the object from the bucket let client = s3.client(); @@ -194,6 +208,7 @@ impl Source { // no-op, but warn log::warn!("Unable to move HTTP source ({url}), skipping!"); } + #[cfg(feature = "s3")] Self::S3(s3) => { let client = s3.client(); client @@ -211,6 +226,7 @@ impl Source { } } +#[cfg(feature = "s3")] #[derive(Clone, Debug, PartialEq, Eq)] pub struct S3 { region: String, @@ -219,6 +235,7 @@ pub struct S3 { key: Option, } +#[cfg(feature = "s3")] impl TryFrom<&str> for S3 { type Error = anyhow::Error; @@ -257,13 +274,14 @@ impl TryFrom<&str> for S3 { } } +#[cfg(feature = "s3")] impl S3 { pub async fn client(&self) -> anyhow::Result { let region_provider = RegionProviderChain::first_try(Region::new(self.region.clone())); let mut shared_config = aws_config::defaults(BehaviorVersion::latest()) .region(region_provider) - .app_name(AppName::new(USER_AGENT)?); + .app_name(AppName::new(crate::USER_AGENT)?); if let Some((key_id, access_key)) = &self.credentials { let credentials = Credentials::new(key_id, access_key, None, None, "config"); @@ -278,8 +296,10 @@ impl S3 { #[cfg(test)] mod tests { + #[allow(unused_imports)] use super::*; + #[cfg(feature = "s3")] #[test] fn parse_s3() { assert_eq!( @@ -311,6 +331,7 @@ mod tests { ); } + #[cfg(feature = "s3")] #[test] fn parse_s3_custom_region() { assert_eq!( diff --git a/csaf/csaf-cli/Cargo.toml b/csaf/csaf-cli/Cargo.toml index 6893c79..f7b5f0f 100644 --- a/csaf/csaf-cli/Cargo.toml +++ b/csaf/csaf-cli/Cargo.toml @@ -34,7 +34,7 @@ openssl = { workspace = true, optional = true } # internal csaf-walker = { workspace = true, features = ["csaf"] } -walker-common = { workspace = true, features = ["openpgp", "clap", "env_logger"] } +walker-common = { workspace = true, features = ["openpgp", "clap", "env_logger", "s3"] } walker-extras = { workspace = true } [features] diff --git a/sbom/sbom-cli/Cargo.toml b/sbom/sbom-cli/Cargo.toml index e51bf24..9728e81 100644 --- a/sbom/sbom-cli/Cargo.toml +++ b/sbom/sbom-cli/Cargo.toml @@ -31,7 +31,7 @@ tokio = { workspace = true, features = ["full"] } # internal sbom-walker = { workspace = true, features = ["serde-cyclonedx", "spdx-rs"] } -walker-common = { workspace = true, features = ["openpgp", "clap", "env_logger"] } +walker-common = { workspace = true, features = ["openpgp", "clap", "env_logger", "s3"] } walker-extras = { workspace = true } # just there for the feature