anodizer_core/config/
verify_release.rs1use schemars::JsonSchema;
28use serde::{Deserialize, Serialize};
29
30pub const DEFAULT_DEB_IMAGE: &str = "debian:stable-slim";
32pub const DEFAULT_RPM_IMAGE: &str = "fedora:latest";
34pub const DEFAULT_APK_IMAGE: &str = "alpine:latest";
36
37#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
42#[serde(default, deny_unknown_fields)]
43pub struct VerifyReleaseConfig {
44 pub enabled: bool,
49 pub assert_assets: bool,
59 pub install_smoke: Option<InstallSmokeConfig>,
64 #[serde(skip_serializing_if = "Option::is_none")]
69 pub glibc_ceiling: Option<String>,
70}
71
72impl Default for VerifyReleaseConfig {
73 fn default() -> Self {
74 Self {
75 enabled: false,
76 assert_assets: true,
77 install_smoke: None,
78 glibc_ceiling: None,
79 }
80 }
81}
82
83impl VerifyReleaseConfig {
84 pub fn assert_assets_enabled(&self) -> bool {
87 self.enabled && self.assert_assets
88 }
89
90 pub fn glibc_check_enabled(&self) -> bool {
93 self.enabled && self.glibc_ceiling.is_some()
94 }
95}
96
97#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
103#[serde(default, deny_unknown_fields)]
104pub struct InstallSmokeConfig {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub deb: Option<SmokeImage>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub rpm: Option<SmokeImage>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub apk: Option<SmokeImage>,
114}
115
116impl InstallSmokeConfig {
117 pub fn deb_image(&self) -> &str {
119 self.deb
120 .as_ref()
121 .map(|i| i.image.as_str())
122 .unwrap_or(DEFAULT_DEB_IMAGE)
123 }
124
125 pub fn rpm_image(&self) -> &str {
127 self.rpm
128 .as_ref()
129 .map(|i| i.image.as_str())
130 .unwrap_or(DEFAULT_RPM_IMAGE)
131 }
132
133 pub fn apk_image(&self) -> &str {
135 self.apk
136 .as_ref()
137 .map(|i| i.image.as_str())
138 .unwrap_or(DEFAULT_APK_IMAGE)
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
144#[serde(deny_unknown_fields)]
145pub struct SmokeImage {
146 pub image: String,
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn default_is_disabled_with_assets_on() {
156 let c = VerifyReleaseConfig::default();
157 assert!(!c.enabled, "the gate is opt-in");
158 assert!(c.assert_assets, "asset-existence defaults on");
159 assert!(c.install_smoke.is_none(), "smoke off by default");
160 assert!(c.glibc_ceiling.is_none(), "libc check off by default");
161 assert!(!c.assert_assets_enabled());
163 assert!(!c.glibc_check_enabled());
164 }
165
166 #[test]
167 fn empty_yaml_yields_defaults() {
168 let c: VerifyReleaseConfig = serde_yaml_ng::from_str("{}").unwrap();
169 assert_eq!(c, VerifyReleaseConfig::default());
170 }
171
172 #[test]
173 fn parses_full_block() {
174 let yaml = r#"
175enabled: true
176assert_assets: true
177install_smoke:
178 deb: { image: "debian:12" }
179 rpm: { image: "fedora:40" }
180 apk: { image: "alpine:3.20" }
181glibc_ceiling: "2.36"
182"#;
183 let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
184 assert!(c.enabled);
185 assert!(c.assert_assets_enabled());
186 assert!(c.glibc_check_enabled());
187 assert_eq!(c.glibc_ceiling.as_deref(), Some("2.36"));
188 let smoke = c.install_smoke.unwrap();
189 assert_eq!(smoke.deb_image(), "debian:12");
190 assert_eq!(smoke.rpm_image(), "fedora:40");
191 assert_eq!(smoke.apk_image(), "alpine:3.20");
192 }
193
194 #[test]
195 fn smoke_images_fall_back_to_defaults() {
196 let yaml = "enabled: true\ninstall_smoke:\n deb: { image: \"debian:12\" }\n";
198 let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
199 let smoke = c.install_smoke.unwrap();
200 assert_eq!(smoke.deb_image(), "debian:12");
201 assert_eq!(smoke.rpm_image(), DEFAULT_RPM_IMAGE);
202 assert_eq!(smoke.apk_image(), DEFAULT_APK_IMAGE);
203 }
204
205 #[test]
206 fn empty_install_smoke_uses_all_defaults() {
207 let yaml = "enabled: true\ninstall_smoke: {}\n";
208 let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
209 let smoke = c.install_smoke.unwrap();
210 assert_eq!(smoke.deb_image(), DEFAULT_DEB_IMAGE);
211 assert_eq!(smoke.rpm_image(), DEFAULT_RPM_IMAGE);
212 assert_eq!(smoke.apk_image(), DEFAULT_APK_IMAGE);
213 }
214
215 #[test]
216 fn glibc_ceiling_absent_disables_check() {
217 let yaml = "enabled: true\n";
218 let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
219 assert!(!c.glibc_check_enabled(), "no ceiling => libc check off");
220 }
221
222 #[test]
223 fn assert_assets_independently_toggleable() {
224 let yaml = "enabled: true\nassert_assets: false\n";
225 let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
226 assert!(!c.assert_assets_enabled(), "asset check opted out");
227 assert!(c.enabled, "gate still enabled for other checks");
228 }
229
230 #[test]
231 fn unknown_field_rejected() {
232 let yaml = "enabled: true\nbogus: 1\n";
233 let res: Result<VerifyReleaseConfig, _> = serde_yaml_ng::from_str(yaml);
234 assert!(res.is_err(), "deny_unknown_fields must reject typos");
235 }
236
237 #[test]
238 fn unknown_smoke_field_rejected() {
239 let yaml = "enabled: true\ninstall_smoke:\n deb: { image: \"x\", typo: 1 }\n";
240 let res: Result<VerifyReleaseConfig, _> = serde_yaml_ng::from_str(yaml);
241 assert!(res.is_err(), "deny_unknown_fields on SmokeImage");
242 }
243}