use indoc::indoc;
use super::*;
fn chart_local_crd_provider(document: &Value) -> Chain {
let mut universe = helm_schema_k8s::LocalSchemaUniverse::default();
for resource_schema in helm_schema_k8s::resource_schemas_from_crd_document_with_source(
document,
"chart-static-crd",
"crds/widget.yaml".to_string(),
) {
universe.insert_resource_schema(resource_schema);
}
Chain::new(vec![
Box::new(helm_schema_k8s::ChartLocalCrdSchemaProvider::new(universe)),
Box::new(
KubernetesJsonSchemaProvider::new("v1.35.0")
.with_cache_dir(bundle_cache_dir())
.with_allow_download(false),
),
])
.with_inference_enabled(true)
}
fn widget_crd(api_version: &str, preserve_unknown_fields: Option<bool>) -> Value {
let mut document = serde_json::json!({
"apiVersion": api_version,
"kind": "CustomResourceDefinition",
"spec": {
"group": "example.com",
"names": { "kind": "Widget" },
"versions": [{
"name": "v1",
"served": true,
"schema": { "openAPIV3Schema": {
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": { "name": { "type": "string" } },
},
"spec": {
"type": "object",
"properties": {
"sink": {
"type": "object",
"properties": { "port": { "type": "integer" } },
},
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": { "action": { "type": "string" } },
},
},
"opaque": {
"type": "object",
"properties": { "action": { "type": "string" } },
"x-kubernetes-preserve-unknown-fields": true,
},
},
},
},
}},
}],
},
});
if let Some(preserve) = preserve_unknown_fields {
document["spec"]["preserveUnknownFields"] = Value::Bool(preserve);
}
document
}
const WIDGET_TEMPLATE: &str = indoc! {r"
apiVersion: example.com/v1
kind: Widget
metadata:
name: test
annotations:
{{- toYaml .Values.annotations | nindent 4 }}
spec:
sink:
{{- toYaml .Values.sink | nindent 4 }}
rules:
{{- toYaml .Values.rules | nindent 4 }}
opaque:
{{- toYaml .Values.opaque | nindent 4 }}
"};
const WIDGET_VALUES: &str = indoc! {"
annotations: {}
sink: {}
rules: []
opaque: {}
"};
#[test]
fn chart_shipped_crds_close_the_fields_their_schema_prunes() {
let provider = chart_local_crd_provider(&widget_crd("apiextensions.k8s.io/v1", None));
let signals = schema_signals_for(parse_ir(WIDGET_TEMPLATE));
let schema = generate_values_schema(
ValuesSchemaInput::new(&signals, &provider)
.with_values_documents(&prepared_values_documents(Some(WIDGET_VALUES))),
);
for (instance, want, label) in [
(
serde_json::json!({ "sink": { "port": 7 } }),
true,
"declared member",
),
(
serde_json::json!({ "sink": { "unknown": 7 } }),
false,
"pruned member",
),
(
serde_json::json!({ "rules": [{ "action": "keep" }] }),
true,
"declared item member",
),
(
serde_json::json!({ "rules": [{ "unknown": "keep" }] }),
false,
"pruned item member",
),
(
serde_json::json!({ "opaque": { "unknown": "keep" } }),
true,
"preserved subtree",
),
(
serde_json::json!({ "annotations": { "example.com/note": "keep" } }),
true,
"metadata is never pruned",
),
] {
assert!(
schema_accepts_instance(&schema, &instance) == want,
"pruning CRD {label}: instance={instance}; schema={schema}"
);
}
}
#[test]
fn non_pruning_crds_keep_their_undeclared_fields_open() {
for (preserve_unknown_fields, want, label) in [
(None, true, "v1beta1 default"),
(Some(true), true, "explicit opt-out"),
(Some(false), false, "explicit pruning"),
] {
let provider = chart_local_crd_provider(&widget_crd(
"apiextensions.k8s.io/v1beta1",
preserve_unknown_fields,
));
let signals = schema_signals_for(parse_ir(WIDGET_TEMPLATE));
let schema = generate_values_schema(
ValuesSchemaInput::new(&signals, &provider)
.with_values_documents(&prepared_values_documents(Some(WIDGET_VALUES))),
);
let instance = serde_json::json!({ "sink": { "unknown": 7 } });
assert!(
schema_accepts_instance(&schema, &instance) == want,
"{label}: instance={instance}; schema={schema}"
);
}
}