helm-schema 0.0.7

Generate an accurate JSON schema for any helm chart
Documentation
use color_eyre::eyre;
use indoc::indoc;

use super::{build_composed_values_document, build_dependency_global_ownership};
use crate::chart::ChartContext;
use crate::chart::discover_chart_contexts;
use test_util::prelude::sim_assert_eq;
use vfs::VfsPath;

fn yaml_pointer<'a>(doc: &'a serde_yaml::Value, path: &[&str]) -> Option<&'a serde_yaml::Value> {
    let mut current = doc;
    for segment in path {
        let map = current.as_mapping()?;
        current = map.get(serde_yaml::Value::String((*segment).to_string()))?;
    }
    Some(current)
}

fn discover(chart_dir: &VfsPath) -> eyre::Result<Vec<ChartContext>> {
    Ok(discover_chart_contexts(chart_dir)?)
}

#[test]
fn composed_subchart_globals_apply_parent_null_deletion() -> eyre::Result<()> {
    let chart_dir = VfsPath::new(vfs::MemoryFS::new());
    test_util::write(
        &chart_dir.join("Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: root
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("values.yaml")?,
        indoc! {"
            global:
              imageRegistry:
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: child
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/values.yaml")?,
        indoc! {"
            global:
              imageRegistry: docker.io
        "},
    )?;

    let doc = build_composed_values_document(&discover(&chart_dir)?, true)?;

    assert!(
        yaml_pointer(&doc, &["global", "imageRegistry"]).is_some_and(serde_yaml::Value::is_null),
        "root explicit null should remain authoritative: {doc:?}"
    );
    assert!(
        yaml_pointer(&doc, &["child", "global"])
            .and_then(serde_yaml::Value::as_mapping)
            .is_some_and(serde_yaml::Mapping::is_empty),
        "the injected null should delete the child default at its coalesce stage: {doc:?}"
    );

    Ok(())
}

#[test]
fn composed_subchart_globals_stay_in_the_child_when_parent_key_is_absent() -> eyre::Result<()> {
    let chart_dir = VfsPath::new(vfs::MemoryFS::new());
    test_util::write(
        &chart_dir.join("Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: root
            version: 0.1.0
        "},
    )?;
    test_util::write(&chart_dir.join("values.yaml")?, "{}\n")?;
    test_util::write(
        &chart_dir.join("charts/child/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: child
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/values.yaml")?,
        indoc! {"
            global:
              imageRegistry: docker.io
        "},
    )?;

    let doc = build_composed_values_document(&discover(&chart_dir)?, true)?;

    sim_assert_eq!(
        have: yaml_pointer(&doc, &["global", "imageRegistry"]),
        want: None
    );
    sim_assert_eq!(
        have: yaml_pointer(&doc, &["child", "global", "imageRegistry"]),
        want: Some(&serde_yaml::Value::String("docker.io".to_string()))
    );

    Ok(())
}

#[test]
fn scalar_parent_global_skips_injection_and_keeps_child_defaults() -> eyre::Result<()> {
    let chart_dir = VfsPath::new(vfs::MemoryFS::new());
    test_util::write(
        &chart_dir.join("Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: root
            version: 0.1.0
        "},
    )?;
    test_util::write(&chart_dir.join("values.yaml")?, "global: disabled\n")?;
    test_util::write(
        &chart_dir.join("charts/child/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: child
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/values.yaml")?,
        indoc! {"
            global:
              imageRegistry: docker.io
        "},
    )?;

    let doc = build_composed_values_document(&discover(&chart_dir)?, true)?;

    sim_assert_eq!(
        have: yaml_pointer(&doc, &["global"]),
        want: Some(&serde_yaml::Value::String("disabled".to_string()))
    );
    sim_assert_eq!(
        have: yaml_pointer(&doc, &["child", "global", "imageRegistry"]),
        want: Some(&serde_yaml::Value::String("docker.io".to_string()))
    );

    Ok(())
}

#[test]
fn scalar_child_global_skips_parent_injection_and_child_defaults() -> eyre::Result<()> {
    let chart_dir = VfsPath::new(vfs::MemoryFS::new());
    test_util::write(
        &chart_dir.join("Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: root
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("values.yaml")?,
        indoc! {"
            global:
              imageRegistry: parent.example
            child:
              global: disabled
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: child
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/child/values.yaml")?,
        indoc! {"
            global:
              imageRegistry: docker.io
        "},
    )?;

    let doc = build_composed_values_document(&discover(&chart_dir)?, true)?;

    sim_assert_eq!(
        have: yaml_pointer(&doc, &["child", "global"]),
        want: Some(&serde_yaml::Value::String("disabled".to_string()))
    );

    Ok(())
}

#[test]
fn dependency_global_ownership_keeps_chart_identity() -> eyre::Result<()> {
    let chart_dir = VfsPath::new(vfs::MemoryFS::new());
    test_util::write(
        &chart_dir.join("Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: root
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("values.yaml")?,
        indoc! {"
            global:
              region: root
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/metrics/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: metrics
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/metrics/values.yaml")?,
        indoc! {r#"
            global:
              imageRegistry: ""
              storageClass:
              labels:
                owner: metrics
        "#},
    )?;
    test_util::write(
        &chart_dir.join("charts/metrics/charts/agent/Chart.yaml")?,
        indoc! {"
            apiVersion: v2
            name: agent
            version: 0.1.0
        "},
    )?;
    test_util::write(
        &chart_dir.join("charts/metrics/charts/agent/values.yaml")?,
        indoc! {"
            global:
              imageRegistry: local
              imagePullSecrets: []
              labels:
                region: local
        "},
    )?;

    let ownership = build_dependency_global_ownership(&discover(&chart_dir)?)?;
    sim_assert_eq!(
        have: ownership.shadowed_input_paths,
        want: std::collections::BTreeSet::from([
            "metrics.agent.global.imageRegistry".to_string(),
            "metrics.agent.global.labels.owner".to_string(),
            "metrics.agent.global.region".to_string(),
            "metrics.agent.global.storageClass".to_string(),
            "metrics.global.region".to_string(),
        ])
    );

    Ok(())
}