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