anodizer_core/config/
sbom.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Deserializer, Serialize};
3
4use super::{StringOrBool, deserialize_string_or_bool_opt};
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
11#[serde(default, deny_unknown_fields)]
12pub struct SbomConfig {
13 pub id: Option<String>,
15 pub cmd: Option<String>,
17 #[serde(default)]
20 pub env: Option<Vec<String>>,
21 pub args: Option<Vec<String>>,
23 pub documents: Option<Vec<String>>,
25 pub artifacts: Option<String>,
27 pub ids: Option<Vec<String>>,
29 #[serde(
32 alias = "disable",
33 deserialize_with = "deserialize_string_or_bool_opt",
34 default
35 )]
36 pub skip: Option<StringOrBool>,
37}
38
39impl SbomConfig {
40 pub const DEFAULT_ID: &'static str = "default";
42
43 pub const DEFAULT_CMD: &'static str = "syft";
46
47 pub const DEFAULT_ARTIFACTS: &'static str = "archive";
50
51 pub const DEFAULT_DOCUMENT_BINARY: &'static str =
55 "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom.json";
56
57 pub const DEFAULT_DOCUMENT_OTHER: &'static str = "{{ .ArtifactName }}.sbom.json";
60
61 pub const DEFAULT_SYFT_ARGS: &[&'static str] = &[
65 "$artifact",
66 "--output",
67 "spdx-json=$document",
68 "--enrich",
69 "all",
70 ];
71
72 pub const DEFAULT_SYFT_ENV_KEY: &'static str = "SYFT_FILE_METADATA_CATALOGER_ENABLED";
75 pub const DEFAULT_SYFT_ENV_VAL: &'static str = "true";
76
77 pub fn resolved_id(&self) -> &str {
79 self.id.as_deref().unwrap_or(Self::DEFAULT_ID)
80 }
81
82 pub fn resolved_cmd(&self) -> &str {
84 self.cmd.as_deref().unwrap_or(Self::DEFAULT_CMD)
85 }
86
87 pub fn resolved_artifacts(&self) -> &str {
89 self.artifacts.as_deref().unwrap_or(Self::DEFAULT_ARTIFACTS)
90 }
91
92 pub fn resolved_documents(&self, artifacts: &str) -> Vec<String> {
96 self.documents.clone().unwrap_or_else(|| match artifacts {
97 "binary" => vec![Self::DEFAULT_DOCUMENT_BINARY.to_string()],
98 "any" => vec![],
99 _ => vec![Self::DEFAULT_DOCUMENT_OTHER.to_string()],
100 })
101 }
102
103 pub fn resolved_args(&self, cmd: &str) -> Vec<String> {
108 self.args.clone().unwrap_or_else(|| {
109 if cmd == Self::DEFAULT_CMD {
110 Self::DEFAULT_SYFT_ARGS
111 .iter()
112 .map(|s| (*s).to_string())
113 .collect()
114 } else {
115 Vec::new()
116 }
117 })
118 }
119
120 pub fn default_syft_env_for(cmd: &str, artifacts: &str) -> Vec<(String, String)> {
125 if cmd == Self::DEFAULT_CMD && matches!(artifacts, "source" | "archive") {
126 vec![(
127 Self::DEFAULT_SYFT_ENV_KEY.to_string(),
128 Self::DEFAULT_SYFT_ENV_VAL.to_string(),
129 )]
130 } else {
131 Vec::new()
132 }
133 }
134}
135
136pub(super) fn deserialize_sboms<'de, D>(deserializer: D) -> Result<Vec<SbomConfig>, D::Error>
142where
143 D: Deserializer<'de>,
144{
145 use serde::de::{self, Visitor};
146
147 struct SbomsVisitor;
148
149 impl<'de> Visitor<'de> for SbomsVisitor {
150 type Value = Vec<SbomConfig>;
151
152 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 f.write_str("an SBOM config object or an array of SBOM config objects")
154 }
155
156 fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
157 let mut configs = Vec::new();
158 while let Some(item) = seq.next_element::<SbomConfig>()? {
159 configs.push(item);
160 }
161 Ok(configs)
162 }
163
164 fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
165 let config = SbomConfig::deserialize(de::value::MapAccessDeserializer::new(map))?;
166 Ok(vec![config])
167 }
168
169 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
170 Ok(Vec::new())
171 }
172
173 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
174 Ok(Vec::new())
175 }
176 }
177
178 deserializer.deserialize_any(SbomsVisitor)
179}