use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub const DEFAULT_DEB_IMAGE: &str = "debian:stable-slim";
pub const DEFAULT_RPM_IMAGE: &str = "fedora:latest";
pub const DEFAULT_APK_IMAGE: &str = "alpine:latest";
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct VerifyReleaseConfig {
pub enabled: bool,
pub assert_assets: bool,
pub install_smoke: Option<InstallSmokeConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub glibc_ceiling: Option<String>,
}
impl Default for VerifyReleaseConfig {
fn default() -> Self {
Self {
enabled: false,
assert_assets: true,
install_smoke: None,
glibc_ceiling: None,
}
}
}
impl VerifyReleaseConfig {
pub fn assert_assets_enabled(&self) -> bool {
self.enabled && self.assert_assets
}
pub fn glibc_check_enabled(&self) -> bool {
self.enabled && self.glibc_ceiling.is_some()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct InstallSmokeConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub deb: Option<SmokeImage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rpm: Option<SmokeImage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub apk: Option<SmokeImage>,
}
impl InstallSmokeConfig {
pub fn deb_image(&self) -> &str {
self.deb
.as_ref()
.map(|i| i.image.as_str())
.unwrap_or(DEFAULT_DEB_IMAGE)
}
pub fn rpm_image(&self) -> &str {
self.rpm
.as_ref()
.map(|i| i.image.as_str())
.unwrap_or(DEFAULT_RPM_IMAGE)
}
pub fn apk_image(&self) -> &str {
self.apk
.as_ref()
.map(|i| i.image.as_str())
.unwrap_or(DEFAULT_APK_IMAGE)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct SmokeImage {
pub image: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_disabled_with_assets_on() {
let c = VerifyReleaseConfig::default();
assert!(!c.enabled, "the gate is opt-in");
assert!(c.assert_assets, "asset-existence defaults on");
assert!(c.install_smoke.is_none(), "smoke off by default");
assert!(c.glibc_ceiling.is_none(), "libc check off by default");
assert!(!c.assert_assets_enabled());
assert!(!c.glibc_check_enabled());
}
#[test]
fn empty_yaml_yields_defaults() {
let c: VerifyReleaseConfig = serde_yaml_ng::from_str("{}").unwrap();
assert_eq!(c, VerifyReleaseConfig::default());
}
#[test]
fn parses_full_block() {
let yaml = r#"
enabled: true
assert_assets: true
install_smoke:
deb: { image: "debian:12" }
rpm: { image: "fedora:40" }
apk: { image: "alpine:3.20" }
glibc_ceiling: "2.36"
"#;
let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
assert!(c.enabled);
assert!(c.assert_assets_enabled());
assert!(c.glibc_check_enabled());
assert_eq!(c.glibc_ceiling.as_deref(), Some("2.36"));
let smoke = c.install_smoke.unwrap();
assert_eq!(smoke.deb_image(), "debian:12");
assert_eq!(smoke.rpm_image(), "fedora:40");
assert_eq!(smoke.apk_image(), "alpine:3.20");
}
#[test]
fn smoke_images_fall_back_to_defaults() {
let yaml = "enabled: true\ninstall_smoke:\n deb: { image: \"debian:12\" }\n";
let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
let smoke = c.install_smoke.unwrap();
assert_eq!(smoke.deb_image(), "debian:12");
assert_eq!(smoke.rpm_image(), DEFAULT_RPM_IMAGE);
assert_eq!(smoke.apk_image(), DEFAULT_APK_IMAGE);
}
#[test]
fn empty_install_smoke_uses_all_defaults() {
let yaml = "enabled: true\ninstall_smoke: {}\n";
let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
let smoke = c.install_smoke.unwrap();
assert_eq!(smoke.deb_image(), DEFAULT_DEB_IMAGE);
assert_eq!(smoke.rpm_image(), DEFAULT_RPM_IMAGE);
assert_eq!(smoke.apk_image(), DEFAULT_APK_IMAGE);
}
#[test]
fn glibc_ceiling_absent_disables_check() {
let yaml = "enabled: true\n";
let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
assert!(!c.glibc_check_enabled(), "no ceiling => libc check off");
}
#[test]
fn assert_assets_independently_toggleable() {
let yaml = "enabled: true\nassert_assets: false\n";
let c: VerifyReleaseConfig = serde_yaml_ng::from_str(yaml).unwrap();
assert!(!c.assert_assets_enabled(), "asset check opted out");
assert!(c.enabled, "gate still enabled for other checks");
}
#[test]
fn unknown_field_rejected() {
let yaml = "enabled: true\nbogus: 1\n";
let res: Result<VerifyReleaseConfig, _> = serde_yaml_ng::from_str(yaml);
assert!(res.is_err(), "deny_unknown_fields must reject typos");
}
#[test]
fn unknown_smoke_field_rejected() {
let yaml = "enabled: true\ninstall_smoke:\n deb: { image: \"x\", typo: 1 }\n";
let res: Result<VerifyReleaseConfig, _> = serde_yaml_ng::from_str(yaml);
assert!(res.is_err(), "deny_unknown_fields on SmokeImage");
}
}