use crate::config;
#[derive(Debug, Clone, serde::Serialize)]
pub struct EffectiveReconcile {
pub interval: String,
pub auto_apply: bool,
pub drift_policy: config::DriftPolicy,
}
pub fn resolve_effective_reconcile(
module_name: &str,
profile_chain: &[&str],
reconcile: &config::ReconcileConfig,
) -> EffectiveReconcile {
let mut effective = EffectiveReconcile {
interval: reconcile.interval.clone(),
auto_apply: reconcile.auto_apply,
drift_policy: reconcile.drift_policy.clone(),
};
if let Some(patch) = reconcile
.patches
.iter()
.rev()
.find(|p| p.kind == config::ReconcilePatchKind::Profile && p.name.is_none())
{
overlay_reconcile_patch(&mut effective, patch);
}
for profile_name in profile_chain {
if let Some(patch) = reconcile.patches.iter().rev().find(|p| {
p.kind == config::ReconcilePatchKind::Profile && p.name.as_deref() == Some(profile_name)
}) {
overlay_reconcile_patch(&mut effective, patch);
}
}
if let Some(patch) = reconcile
.patches
.iter()
.rev()
.find(|p| p.kind == config::ReconcilePatchKind::Module && p.name.is_none())
{
overlay_reconcile_patch(&mut effective, patch);
}
if let Some(patch) = reconcile.patches.iter().rev().find(|p| {
p.kind == config::ReconcilePatchKind::Module && p.name.as_deref() == Some(module_name)
}) {
overlay_reconcile_patch(&mut effective, patch);
}
effective
}
fn overlay_reconcile_patch(base: &mut EffectiveReconcile, patch: &config::ReconcilePatch) {
if let Some(ref interval) = patch.interval {
base.interval = interval.clone();
}
if let Some(auto_apply) = patch.auto_apply {
base.auto_apply = auto_apply;
}
if let Some(ref dp) = patch.drift_policy {
base.drift_policy = dp.clone();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_reconcile_config(patches: Vec<config::ReconcilePatch>) -> config::ReconcileConfig {
config::ReconcileConfig {
interval: "5m".into(),
on_change: false,
auto_apply: false,
policy: None,
drift_policy: config::DriftPolicy::NotifyOnly,
patches,
}
}
#[test]
fn no_patches_returns_global_defaults() {
let rc = make_reconcile_config(vec![]);
let eff = resolve_effective_reconcile("docker", &["base"], &rc);
assert_eq!(eff.interval, "5m");
assert!(!eff.auto_apply);
assert_eq!(eff.drift_policy, config::DriftPolicy::NotifyOnly);
}
#[test]
fn named_module_patch_overrides_global() {
let rc = make_reconcile_config(vec![config::ReconcilePatch {
kind: config::ReconcilePatchKind::Module,
name: Some("docker".into()),
interval: Some("1m".into()),
auto_apply: Some(true),
drift_policy: Some(config::DriftPolicy::Auto),
}]);
let eff = resolve_effective_reconcile("docker", &["base"], &rc);
assert_eq!(eff.interval, "1m");
assert!(eff.auto_apply);
assert_eq!(eff.drift_policy, config::DriftPolicy::Auto);
}
#[test]
fn named_module_patch_does_not_affect_other_modules() {
let rc = make_reconcile_config(vec![config::ReconcilePatch {
kind: config::ReconcilePatchKind::Module,
name: Some("docker".into()),
interval: Some("1m".into()),
auto_apply: None,
drift_policy: None,
}]);
let eff = resolve_effective_reconcile("kubernetes", &["base"], &rc);
assert_eq!(eff.interval, "5m");
}
#[test]
fn kind_wide_module_patch_applies_to_all() {
let rc = make_reconcile_config(vec![config::ReconcilePatch {
kind: config::ReconcilePatchKind::Module,
name: None,
interval: Some("2m".into()),
auto_apply: None,
drift_policy: None,
}]);
let eff = resolve_effective_reconcile("anything", &["base"], &rc);
assert_eq!(eff.interval, "2m");
}
#[test]
fn named_profile_patch_applies_when_in_chain() {
let rc = make_reconcile_config(vec![config::ReconcilePatch {
kind: config::ReconcilePatchKind::Profile,
name: Some("work".into()),
interval: Some("10m".into()),
auto_apply: Some(true),
drift_policy: None,
}]);
let eff = resolve_effective_reconcile("docker", &["base", "work"], &rc);
assert_eq!(eff.interval, "10m");
assert!(eff.auto_apply);
}
#[test]
fn named_module_beats_named_profile() {
let rc = make_reconcile_config(vec![
config::ReconcilePatch {
kind: config::ReconcilePatchKind::Profile,
name: Some("work".into()),
interval: Some("10m".into()),
auto_apply: None,
drift_policy: None,
},
config::ReconcilePatch {
kind: config::ReconcilePatchKind::Module,
name: Some("docker".into()),
interval: Some("30s".into()),
auto_apply: None,
drift_policy: None,
},
]);
let eff = resolve_effective_reconcile("docker", &["base", "work"], &rc);
assert_eq!(eff.interval, "30s");
}
#[test]
fn kind_wide_profile_patch_applies() {
let rc = make_reconcile_config(vec![config::ReconcilePatch {
kind: config::ReconcilePatchKind::Profile,
name: None,
interval: None,
auto_apply: Some(true),
drift_policy: Some(config::DriftPolicy::Auto),
}]);
let eff = resolve_effective_reconcile("docker", &["base"], &rc);
assert!(eff.auto_apply);
assert_eq!(eff.drift_policy, config::DriftPolicy::Auto);
}
}