use clap::Parser;
use color_eyre::eyre::{self, OptionExt as _, WrapErr, eyre};
use helm_schema::AnalysisSession;
use helm_schema_cli::{Cli, GenerateOptions, ProviderOptions, SchemaProfile};
use indoc::indoc;
use test_util::prelude::sim_assert_eq;
use vfs::VfsPath;
fn into_eyre(e: helm_schema_cli::CliError) -> eyre::Report {
e.into()
}
fn generate_values_schema_for_chart(
opts: &GenerateOptions,
) -> helm_schema::EngineResult<serde_json::Value> {
AnalysisSession::new(opts.clone())
.generated_schema()
.map(|generated| generated.schema)
}
fn navigated_host_clause(segments: &[&str]) -> serde_json::Value {
let chain = |leaf: serde_json::Value| {
let mut node = leaf;
for segment in segments.iter().rev() {
node = serde_json::json!({
"type": "object",
"required": [*segment],
"properties": { *segment: node },
});
}
node
};
serde_json::json!({
"if": { "anyOf": [
{ "not": chain(serde_json::json!({})) },
chain(serde_json::json!({ "enum": [null] })),
] },
"then": false,
})
}
fn schema_accepts_type(schema: &serde_json::Value, schema_type: &str) -> bool {
(match schema.get("type") {
Some(serde_json::Value::String(value)) => value == schema_type,
Some(serde_json::Value::Array(values)) => values
.iter()
.any(|value| value.as_str() == Some(schema_type)),
_ => false,
}) || schema
.get("anyOf")
.and_then(serde_json::Value::as_array)
.is_some_and(|variants| {
variants
.iter()
.any(|variant| schema_accepts_type(variant, schema_type))
})
|| schema
.get("oneOf")
.and_then(serde_json::Value::as_array)
.is_some_and(|variants| {
variants
.iter()
.any(|variant| schema_accepts_type(variant, schema_type))
})
}
fn schema_contains_open_string_map(schema: &serde_json::Value) -> bool {
schema
.pointer("/additionalProperties/type")
.and_then(serde_json::Value::as_str)
== Some("string")
|| ["anyOf", "oneOf"].into_iter().any(|keyword| {
schema
.get(keyword)
.and_then(serde_json::Value::as_array)
.is_some_and(|variants| variants.iter().any(schema_contains_open_string_map))
})
}
fn schema_validates_instance(
schema: &serde_json::Value,
instance: &serde_json::Value,
) -> eyre::Result<bool> {
Ok(jsonschema::validator_for(schema)?.is_valid(instance))
}
#[test]
fn cli_parses_defaults() -> eyre::Result<()> {
let cli =
Cli::try_parse_from(["helm-schema", "/tmp/chart"]).map_err(|e| eyre!(e.to_string()))?;
sim_assert_eq!(have: cli.k8s.k8s_version, want: vec!["v1.35.0".to_string()]);
assert!(cli.output.output.is_none());
assert!(!cli.k8s.offline);
assert!(!cli.k8s.no_k8s_schemas);
assert!(!cli.chart.exclude_tests);
assert!(!cli.chart.no_subchart_values);
assert!(cli.chart.values_files.is_empty());
assert!(cli.override_schema.is_empty());
assert!(!cli.output.compact);
Ok(())
}
#[test]
fn override_schema_flag_is_repeatable() -> eyre::Result<()> {
let cli = Cli::try_parse_from([
"helm-schema",
"/tmp/chart",
"--override-schema",
"/tmp/shared.json",
"--override-schema",
"/tmp/per-chart.json",
])
.map_err(|e| eyre!(e.to_string()))?;
let paths: Vec<_> = cli
.override_schema
.iter()
.map(|p| p.to_string_lossy().into_owned())
.collect();
sim_assert_eq!(have: paths, want: vec!["/tmp/shared.json", "/tmp/per-chart.json"]);
Ok(())
}
#[test]
fn generates_schema_for_fixture_chart_without_k8s_provider() -> eyre::Result<()> {
let chart_dir = test_util::workspace_testdata().join("fixture-charts/full-fixture");
let chart_dir_str = chart_dir.to_string_lossy().to_string();
let chart_dir = VfsPath::new(vfs::PhysicalFS::new(&chart_dir_str));
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let defaults_yaml = test_util::read_testdata("fixture-charts/full-fixture/values.yaml")?;
let defaults: serde_json::Value =
serde_yaml::from_str(&defaults_yaml).wrap_err("parse fixture defaults")?;
let validator = jsonschema::validator_for(&actual)?;
let ingress_case =
|enabled: serde_json::Value, hosts: serde_json::Value| -> eyre::Result<bool> {
let mut instance = defaults.clone();
let ingress = instance
.pointer_mut("/ingress")
.and_then(serde_json::Value::as_object_mut)
.ok_or_eyre("fixture ingress defaults")?;
ingress.insert("enabled".to_string(), enabled);
ingress.insert("hosts".to_string(), hosts);
Ok(validator.is_valid(&instance))
};
assert!(
ingress_case(serde_json::json!("yes"), serde_json::json!([]))?,
"a truthy non-Boolean ingress flag renders"
);
assert!(
!ingress_case(serde_json::json!(true), serde_json::json!([7]))?,
"live ingress host members must support member access"
);
assert!(
ingress_case(serde_json::json!(false), serde_json::json!([7]))?,
"dormant ingress host members stay unconstrained"
);
if std::env::var("SCHEMA_DUMP").is_ok() {
let path =
std::env::temp_dir().join("helm-schema.cli.full-fixture.disable-k8s.schema.json");
std::fs::write(
&path,
serde_json::to_vec_pretty(&actual).wrap_err("serialize schema dump")?,
)
.wrap_err("write schema dump")?;
}
let expected: serde_json::Value = serde_json::from_str(include_str!(
"fixtures/full_fixture.disable_k8s.schema.json"
))
.wrap_err("parse expected schema fixture")?;
sim_assert_eq!(have: actual, want: expected);
Ok(())
}
#[test]
fn values_yaml_comments_become_descriptions_without_creating_paths() -> 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! {"
# -- Root enabled docs
enabled: true
# -- Comment-only docs
# commentedOnly: true
"},
)?;
test_util::write(
&chart_dir.join("templates/cm.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: root
data:
enabled: "{{ .Values.enabled }}"
"#},
)?;
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! {r#"
image:
# -- Child image tag docs
tag: "1.0.0"
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/cm.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: child
data:
tag: "{{ .Values.image.tag }}"
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
sim_assert_eq!(
have: actual
.pointer("/properties/enabled/description")
.and_then(serde_json::Value::as_str),
want: Some("Root enabled docs")
);
sim_assert_eq!(
have: actual
.pointer("/properties/child/properties/image/properties/tag/description")
.and_then(serde_json::Value::as_str),
want: Some("Child image tag docs")
);
assert!(
actual.pointer("/properties/commentedOnly").is_none(),
"comment-only values must not create schema paths: {actual}"
);
Ok(())
}
#[test]
fn chart_yaml_dependency_activation_paths_become_boolean_schema() -> 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
dependencies:
- name: child
alias: kid
version: 0.1.0
condition: kid.enabled, global.kidEnabled
tags:
- observability
"},
)?;
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")?, "{}\n")?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
assert!(
schema_accepts_type(
actual
.pointer("/properties/kid/properties/enabled")
.ok_or_eyre("missing kid.enabled schema")?,
"boolean"
),
"expected kid.enabled to be boolean: {actual}"
);
assert!(
schema_accepts_type(
actual
.pointer("/properties/global/properties/kidEnabled")
.ok_or_eyre("missing global.kidEnabled schema")?,
"boolean"
),
"expected global.kidEnabled to be boolean: {actual}"
);
assert!(
schema_accepts_type(
actual
.pointer("/properties/tags/properties/observability")
.ok_or_eyre("missing tags.observability schema")?,
"boolean"
),
"expected tags.observability to be boolean: {actual}"
);
Ok(())
}
#[test]
fn static_chart_crds_type_custom_resource_values() -> 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! {"
widget:
spec: {}
"},
)?;
test_util::write(
&chart_dir.join("crds/widgets.example.com.yaml")?,
indoc! {"
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: widgets.example.com
spec:
group: example.com
names:
kind: Widget
plural: widgets
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: integer
"},
)?;
test_util::write(
&chart_dir.join("templates/widget.yaml")?,
indoc! {r"
apiVersion: example.com/v1
kind: Widget
metadata:
name: widget
spec:
size: {{ .Values.widget.spec.size }}
"},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
..Default::default()
},
};
let schema = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let size = schema
.pointer("/properties/widget/properties/spec/properties/size")
.ok_or_eyre(format!("missing widget.spec.size schema: {schema}"))?;
assert!(
schema_accepts_type(size, "integer"),
"chart-local CRD should type widget.spec.size as integer, got {size}"
);
assert!(
schema_accepts_type(size, "string"),
"an unquoted numeric string reparses to the CRD's integer field, got {size}"
);
Ok(())
}
#[test]
fn reachable_helper_default_type_hint_applies_without_k8s_provider() -> 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! {"
alertmanager:
enabled: true
name: alertmanager
serviceAccount:
create: true
name:
"},
)?;
test_util::write(
&chart_dir.join("templates/_helpers.tpl")?,
indoc! {r#"
{{- define "alertmanager.fullname" -}}
{{- printf "%s-%s" "release" .Values.alertmanager.name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "alertmanager.serviceAccountName" -}}
{{- if .Values.alertmanager.serviceAccount.create -}}
{{ default (include "alertmanager.fullname" .) .Values.alertmanager.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.alertmanager.serviceAccount.name }}
{{- end -}}
{{- end -}}
"#},
)?;
test_util::write(
&chart_dir.join("templates/serviceaccount.yaml")?,
indoc! {r#"
{{- if .Values.alertmanager.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "alertmanager.serviceAccountName" . }}
{{- end }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
..Default::default()
},
};
let schema = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
assert!(
schema_validates_instance(
&schema,
&serde_json::json!({
"alertmanager": {
"enabled": true,
"name": "alertmanager",
"serviceAccount": {
"create": true,
"name": null
}
}
})
)?,
"defaulted helper serviceAccount.name should validate null without provider schemas: {schema}"
);
assert!(
schema_validates_instance(
&schema,
&serde_json::json!({
"alertmanager": {
"enabled": true,
"name": "alertmanager",
"serviceAccount": {
"create": false,
"name": null
}
}
})
)?,
"helper else-branch default should also validate null without provider schemas: {schema}"
);
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the complete fixture scenario is clearest as one contiguous test"
)]
fn layered_values_file_comments_override_and_add_descriptions_only() -> eyre::Result<()> {
let chart_dir = VfsPath::new(vfs::MemoryFS::new());
test_util::write(
&chart_dir.join("Chart.yaml")?,
indoc! {"
apiVersion: v2
name: layered
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
# -- Chart enabled docs
enabled: true
replicas: 1
image:
tag: latest
"},
)?;
test_util::write(
&chart_dir.join("templates/cm.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: layered
data:
enabled: "{{ .Values.enabled }}"
replicas: "{{ .Values.replicas }}"
tag: "{{ .Values.image.tag }}"
"#},
)?;
let temp_dir = std::env::temp_dir().join(format!(
"helm-schema-layered-values-comments-{}",
std::process::id()
));
std::fs::create_dir_all(&temp_dir)?;
let layer_one = temp_dir.join("layer-one.yaml");
let layer_two = temp_dir.join("layer-two.yaml");
std::fs::write(
&layer_one,
indoc! {"
# -- Layer one replicas docs
replicas: 2
## @param layerOnly This comment must not create a schema path
# layerOnly: true
"},
)?;
std::fs::write(
&layer_two,
indoc! {"
# -- Layer two enabled docs
enabled: false
image:
# -- Layer two image tag docs
tag: stable
"},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: vec![layer_one, layer_two],
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
sim_assert_eq!(
have: actual
.pointer("/properties/enabled/description")
.and_then(serde_json::Value::as_str),
want: Some("Layer two enabled docs")
);
sim_assert_eq!(
have: actual
.pointer("/properties/replicas/description")
.and_then(serde_json::Value::as_str),
want: Some("Layer one replicas docs")
);
sim_assert_eq!(
have: actual
.pointer("/properties/image/properties/tag/description")
.and_then(serde_json::Value::as_str),
want: Some("Layer two image tag docs")
);
assert!(
actual.pointer("/properties/layerOnly").is_none(),
"values-file comments must not create schema paths: {actual}"
);
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the parent/subchart fixture set and its scoping assertions belong to one contiguous scenario"
)]
fn subchart_values_are_scoped_to_the_coalesced_child_view() -> 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
dependencies:
- name: child
alias: kid
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
kid:
persistence:
enabled: true
"},
)?;
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! {"
foo: 1
global:
bar: true
"},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/configmap.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
foo: {{ .Values.foo | quote }}
bar: {{ .Values.global.bar | quote }}
has-bar: {{ hasKey .Values.global "bar" | quote }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let child_global_defaults_schema = serde_json::json!({
"allOf": [
{
"allOf": [
{ "additionalProperties": {} },
{ "type": "object" }
]
},
{ "type": "object" }
]
});
let expected = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"allOf": [
{
"additionalProperties": {},
"properties": { "kid": { "type": ["null", "object"] } }
}
],
"properties": {
"global": {
"additionalProperties": {},
"properties": { "bar": {} }
},
"kid": {
"additionalProperties": {},
"allOf": [
{
"if": {
"allOf": [
{ "type": "object" },
{
"properties": { "global": { "enum": [null] } },
"required": ["global"],
"type": "object"
}
]
},
"then": false
}
],
"properties": {
"foo": {},
"global": child_global_defaults_schema,
"persistence": {
"additionalProperties": {},
"properties": {
"enabled": {
"type": "boolean"
}
},
"type": "object"
},
},
"type": "object"
}
},
"type": "object"
});
sim_assert_eq!(have: actual, want: expected);
let validator = jsonschema::validator_for(&actual)?;
assert!(
validator.is_valid(&serde_json::json!({})),
"the child global default satisfies whole-global map operands when the parent global is absent"
);
assert!(
validator.is_valid(&serde_json::json!({ "global": "disabled" })),
"a scalar source global skips Helm injection"
);
assert!(
!validator.is_valid(&serde_json::json!({ "kid": { "global": "disabled" } })),
"a scalar child global reaches the child member access and aborts"
);
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the dependency defaults, full expected schema, and validation belong to one regression"
)]
fn whole_global_range_accepts_a_child_default_when_the_parent_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
dependencies:
- name: child
alias: kid
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:
enabled: true
"},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/configmap.yaml")?,
indoc! {r"
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
{{- range $key, $value := .Values.global }}
global-{{ $key }}: {{ $value | quote }}
{{- end }}
"},
)?;
let actual = generate_values_schema_for_chart(&GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
})
.map_err(into_eyre)
.wrap_err("generate schema")?;
let expected = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"allOf": [
{
"additionalProperties": {},
"properties": {
"global": { "type": ["array", "null", "object"] }
}
},
{
"additionalProperties": {},
"properties": {
"kid": {
"additionalProperties": {},
"properties": {
"global": { "type": ["array", "null", "object"] }
}
}
}
},
{
"additionalProperties": {},
"properties": { "kid": { "type": ["null", "object"] } }
}
],
"properties": {
"global": { "type": ["array", "null", "object"] },
"kid": {
"additionalProperties": {},
"properties": {
"global": {
"anyOf": [
{
"additionalProperties": { "type": "boolean" },
"properties": { "enabled": { "type": "boolean" } },
"type": "object"
},
{ "items": {}, "type": "array" },
{ "type": "null" },
{ "additionalProperties": {}, "type": "object" }
]
}
},
"type": "object"
}
},
"type": "object"
});
sim_assert_eq!(have: actual, want: expected);
let validator = jsonschema::validator_for(&actual)?;
assert!(
validator.is_valid(&serde_json::json!({
"kid": { "global": { "enabled": true } }
})),
"the coalesced child default satisfies the range while the parent global is absent"
);
Ok(())
}
#[test]
fn subchart_explicit_null_scalar_defaults_stay_nullable_after_string_context() -> 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
dependencies:
- name: child
alias: kid
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! {"
image:
repository: example/app
tag: latest
digest:
"},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/_helpers.tpl")?,
indoc! {r#"
{{- define "child.image" }}
{{- if .digest }}
image: {{ printf "%s@%s" .repository .digest | quote }}
{{- else }}
image: {{ printf "%s:%s" .repository .tag | quote }}
{{- end }}
{{- end }}
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/configmap.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: child
data:
{{- include "child.image" .Values.image | nindent 2 }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let digest = actual
.pointer("/properties/kid/properties/image/properties/digest")
.ok_or_eyre(format!("missing kid.image.digest schema: {actual}"))?;
for probe in [
serde_json::json!("sha256:abc"),
serde_json::json!(null),
serde_json::json!(7),
] {
assert!(
schema_validates_instance(digest, &probe)?,
"kid.image.digest renders through printf, so {probe} must validate, got {digest}"
);
}
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the parent/subchart fixture set and its widening assertions belong to one contiguous scenario"
)]
fn subchart_helper_descendant_access_does_not_widen_parent_objects() -> 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
dependencies:
- name: child
alias: kid
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! {r#"
global:
defaultStorageClass: ""
persistence:
enabled: true
storageClass: ""
size: 1Gi
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/_helpers.tpl")?,
indoc! {r#"
{{- define "common.storage.class" -}}
{{- $storageClass := (.global).storageClass | default .persistence.storageClass | default (.global).defaultStorageClass | default "" -}}
{{- if $storageClass -}}
storageClassName: {{ $storageClass }}
{{- end -}}
{{- end -}}
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/pvc.yaml")?,
indoc! {r#"
apiVersion: v1
kind: PersistentVolumeClaim
spec:
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 2 }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let global = actual
.pointer("/properties/global")
.ok_or_eyre("missing global schema")?;
assert!(
global.get("required").is_none(),
"global should not inherit required fields from helper output, got {global}"
);
assert!(
global.pointer("/properties/kind").is_none(),
"global should not gain typed-local-object-reference fields, got {global}"
);
assert!(
global.pointer("/properties/selector").is_none(),
"global should not gain pvc-spec fields from helper placement, got {global}"
);
let persistence = actual
.pointer("/properties/kid/properties/persistence")
.ok_or_eyre("missing kid.persistence schema")?;
assert!(
persistence.get("required").is_none(),
"kid.persistence should not inherit required fields from helper output, got {persistence}"
);
assert!(
persistence.pointer("/properties/kind").is_none(),
"kid.persistence should not gain typed-local-object-reference fields, got {persistence}"
);
assert!(
persistence.pointer("/properties/selector").is_none(),
"kid.persistence should not gain pvc-spec sibling fields from helper placement, got {persistence}"
);
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the library/subchart fixture set and its widening assertions belong to one contiguous scenario"
)]
fn library_subchart_helper_descendant_access_does_not_widen_parent_objects() -> 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
dependencies:
- name: child
alias: kid
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
dependencies:
- name: common
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("charts/child/values.yaml")?,
indoc! {r#"
global:
defaultStorageClass: ""
persistence:
enabled: true
storageClass: ""
size: 1Gi
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/templates/pvc.yaml")?,
indoc! {r#"
apiVersion: v1
kind: PersistentVolumeClaim
spec:
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 2 }}
"#},
)?;
test_util::write(
&chart_dir.join("charts/child/charts/common/Chart.yaml")?,
indoc! {"
apiVersion: v2
name: common
version: 0.1.0
type: library
"},
)?;
test_util::write(
&chart_dir.join("charts/child/charts/common/templates/_storage.tpl")?,
indoc! {r#"
{{- define "common.storage.class" -}}
{{- $storageClass := (.global).storageClass | default .persistence.storageClass | default (.global).defaultStorageClass | default "" -}}
{{- if $storageClass -}}
storageClassName: {{ $storageClass }}
{{- end -}}
{{- end -}}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let global = actual
.pointer("/properties/global")
.ok_or_eyre("missing global schema")?;
assert!(
global.get("required").is_none(),
"global should not inherit required fields from library helper output, got {global}"
);
assert!(
global.pointer("/properties/kind").is_none(),
"global should not gain typed-local-object-reference fields, got {global}"
);
assert!(
global.pointer("/properties/selector").is_none(),
"global should not gain pvc-spec fields from library helper placement, got {global}"
);
let persistence = actual
.pointer("/properties/kid/properties/persistence")
.ok_or_eyre("missing kid.persistence schema")?;
assert!(
persistence.get("required").is_none(),
"kid.persistence should not inherit required fields from library helper output, got {persistence}"
);
assert!(
persistence.pointer("/properties/kind").is_none(),
"kid.persistence should not gain typed-local-object-reference fields, got {persistence}"
);
assert!(
persistence.pointer("/properties/selector").is_none(),
"kid.persistence should not gain pvc-spec sibling fields from library helper placement, got {persistence}"
);
Ok(())
}
#[test]
fn deployment_annotations_fragment_stays_annotations_map() -> 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")?, "podAnnotations: {}\n")?;
test_util::write(
&chart_dir.join("templates/deployment.yaml")?,
indoc! {r#"
apiVersion: apps/v1
kind: Deployment
spec:
selector:
matchLabels:
app: demo
template:
metadata:
annotations:
checksum/secret: {{ "abc" | quote }}
{{- if .Values.podAnnotations }}
{{ toYaml .Values.podAnnotations | indent 8 }}
{{- end }}
spec:
containers:
- name: demo
image: nginx
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let pod_annotations = actual
.pointer("/properties/podAnnotations")
.ok_or_eyre("missing podAnnotations schema")?;
assert!(
pod_annotations.get("required").is_none(),
"podAnnotations should not inherit deployment required fields, got {pod_annotations}"
);
assert!(
schema_contains_open_string_map(pod_annotations),
"podAnnotations should be an open annotations string map, got {pod_annotations}"
);
Ok(())
}
#[test]
fn defaulted_global_image_pull_secrets_do_not_widen_global_parent() -> 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: {}
imagePullSecrets: []
"},
)?;
test_util::write(
&chart_dir.join("templates/pod.yaml")?,
indoc! {r"
apiVersion: v1
kind: Pod
spec:
{{- with (.Values.imagePullSecrets | default .Values.global.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 4 }}
{{- end }}
containers:
- name: demo
image: nginx
"},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let global = actual
.pointer("/properties/global")
.ok_or_eyre("missing global schema")?;
assert!(
global.get("required").is_none(),
"global should not inherit local-object-reference requirements, got {global}"
);
assert!(
global.pointer("/properties/name").is_none(),
"global should not inherit local-object-reference fields, got {global}"
);
Ok(())
}
#[test]
fn parens_around_values_prefix_propagate_full_path_into_schema() -> 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! {"
image:
repository: example/app
tag: latest
"},
)?;
test_util::write(
&chart_dir.join("templates/deployment.yaml")?,
indoc! {r#"
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ (.Values.image).tag }}"
env:
- name: VARIANT
value: {{ ((.Values.image)).tag | quote }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let image = actual
.pointer("/properties/image")
.ok_or_eyre("missing image schema")?;
assert!(
image.pointer("/properties/tag").is_some(),
"image.tag should be inferred even when the template uses `(.Values.image).tag` parens form; got {image}",
);
assert!(
image.pointer("/properties/repository").is_some(),
"image.repository should still be inferred alongside the parens-form access; got {image}",
);
Ok(())
}
#[test]
fn parens_form_does_not_lose_default_driven_nullability_on_inner_field() -> 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! {r#"
image:
repository: example/app
tag: ""
"#},
)?;
test_util::write(
&chart_dir.join("templates/deployment.yaml")?,
indoc! {r#"
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ (.Values.image).tag | default .Chart.AppVersion }}"
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
assert!(
schema_validates_instance(
&actual,
&serde_json::json!({
"image": {
"repository": "example/app",
"tag": null
}
})
)?,
"image.tag should accept null when guarded by `| default` even through the parens-form prefix; got {actual}",
);
Ok(())
}
#[test]
fn helper_set_default_mutation_widens_target_path_to_nullable() -> eyre::Result<()> {
let chart_dir = VfsPath::new(vfs::MemoryFS::new());
test_util::write(
&chart_dir.join("Chart.yaml")?,
indoc! {"
apiVersion: v2
name: synth-nats
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
serviceAccount:
name:
labels: {}
"},
)?;
test_util::write(
&chart_dir.join("templates/_helpers.tpl")?,
indoc! {r#"
{{- define "synth.fullname" -}}
{{- .Release.Name | default "synth" -}}
{{- end }}
{{- define "synth.defaultValues" }}
{{- $name := include "synth.fullname" . }}
{{- with .Values }}
{{- $_ := set .serviceAccount "name" (.serviceAccount.name | default $name) }}
{{- end }}
{{- end }}
"#},
)?;
test_util::write(
&chart_dir.join("templates/sa.yaml")?,
indoc! {r#"
{{- include "synth.defaultValues" . }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name | quote }}
labels:
{{- toYaml .Values.serviceAccount.labels | nindent 4 }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let expected = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"allOf": [navigated_host_clause(&["serviceAccount"])],
"properties": {
"serviceAccount": {
"additionalProperties": {},
"properties": {
"labels": {
"additionalProperties": { "type": "string" },
"type": "object"
},
"name": {}
},
"type": "object"
}
},
"type": "object"
});
sim_assert_eq!(have: actual, want: expected);
Ok(())
}
#[test]
fn helper_set_with_unrelated_default_does_not_widen_target_path() -> eyre::Result<()> {
let chart_dir = VfsPath::new(vfs::MemoryFS::new());
test_util::write(
&chart_dir.join("Chart.yaml")?,
indoc! {"
apiVersion: v2
name: synth-negative
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
serviceAccount:
name:
other:
"},
)?;
test_util::write(
&chart_dir.join("templates/_helpers.tpl")?,
indoc! {r#"
{{- define "synth.defaultValues" }}
{{- with .Values }}
{{- $_ := set .serviceAccount "name" (printf "%s" (.other | default "fallback")) }}
{{- end }}
{{- end }}
"#},
)?;
test_util::write(
&chart_dir.join("templates/sa.yaml")?,
indoc! {r#"
{{- include "synth.defaultValues" . }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name | quote }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let expected = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"allOf": [navigated_host_clause(&["serviceAccount"])],
"properties": {
"other": {},
"serviceAccount": {
"additionalProperties": {},
"properties": {
"name": {}
},
"type": "object"
}
},
"type": "object"
});
sim_assert_eq!(have: actual, want: expected);
Ok(())
}
#[test]
fn helper_set_default_mutation_in_branch_does_not_leak_to_later_reads() -> eyre::Result<()> {
let chart_dir = VfsPath::new(vfs::MemoryFS::new());
test_util::write(
&chart_dir.join("Chart.yaml")?,
indoc! {"
apiVersion: v2
name: synth-branch-default
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
enabled: false
serviceAccount:
name:
"},
)?;
test_util::write(
&chart_dir.join("templates/_helpers.tpl")?,
indoc! {r#"
{{- define "synth.defaultValues" }}
{{- with .Values }}
{{- $_ := set .serviceAccount "name" (.serviceAccount.name | default "synth") }}
{{- end }}
{{- end }}
"#},
)?;
test_util::write(
&chart_dir.join("templates/sa.yaml")?,
indoc! {r#"
{{- if .Values.enabled }}
{{- include "synth.defaultValues" . }}
{{- end }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name | quote }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: Some(test_util::cold_provider_cache_root("k8s")),
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: false,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let name = actual
.pointer("/properties/serviceAccount/properties/name")
.ok_or_eyre(format!("missing serviceAccount.name schema: {actual}"))?;
assert!(
!schema_accepts_type(name, "null"),
"branch-local default mutation must not make later unconditional reads nullable: {name}"
);
Ok(())
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "the nested helper fixture and nullability assertions form one focused regression"
)]
fn nested_printf_around_common_fullname_keeps_name_overrides_nullable() -> eyre::Result<()> {
let chart_dir = VfsPath::new(vfs::MemoryFS::new());
test_util::write(
&chart_dir.join("Chart.yaml")?,
indoc! {"
apiVersion: v2
name: hs-nested
version: 0.1.0
"},
)?;
test_util::write(
&chart_dir.join("values.yaml")?,
indoc! {"
nameOverride:
fullnameOverride:
"},
)?;
test_util::write(
&chart_dir.join("templates/_helpers.tpl")?,
indoc! {r#"
{{- define "common.names.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
"#},
)?;
test_util::write(
&chart_dir.join("templates/cm.yaml")?,
indoc! {r#"
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-sfx" (include "common.names.fullname" .) }}
"#},
)?;
let opts = GenerateOptions {
chart_dir,
include_tests: false,
include_subchart_values: true,
values_files: Vec::new(),
infer_required: false,
emission: SchemaProfile::default().into(),
provider: ProviderOptions {
k8s_versions: vec!["v1.35.0".to_string()],
k8s_schema_cache_dir: None,
allow_net: false,
crd_catalog_cache_dir: Some(test_util::cold_provider_cache_root("crd")),
disable_k8s_schemas: true,
crd_override_dir: None,
..Default::default()
},
};
let actual = generate_values_schema_for_chart(&opts)
.map_err(into_eyre)
.wrap_err("generate schema")?;
let expected = serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"$defs": {
"t": {
"anyOf": [
{ "const": true },
{ "not": { "const": 0 }, "type": "number" },
{ "minLength": 1, "type": "string" },
{ "minItems": 1, "type": "array" },
{ "minProperties": 1, "type": "object" }
]
}
},
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"fullnameOverride": { "$ref": "#/$defs/t" }
},
"required": ["fullnameOverride"],
"type": "object"
},
"then": {
"additionalProperties": {},
"properties": {
"fullnameOverride": {
"type": ["null", "string"]
}
}
}
},
{
"if": {
"allOf": [
{
"properties": {
"nameOverride": { "$ref": "#/$defs/t" }
},
"required": ["nameOverride"],
"type": "object"
},
{
"not": {
"properties": {
"fullnameOverride": { "$ref": "#/$defs/t" }
},
"required": ["fullnameOverride"],
"type": "object"
}
}
]
},
"then": {
"additionalProperties": {},
"properties": {
"nameOverride": {
"type": ["null", "string"]
}
}
}
}
],
"properties": {
"fullnameOverride": {},
"nameOverride": {}
},
"type": "object"
});
sim_assert_eq!(have: actual, want: expected);
Ok(())
}