Skip to content

fix: do not read a CR's chart-rendered nulls as drift on the three-way merge - #1093

Merged
yxxhero merged 1 commit into
databus23:masterfrom
slamdev:drop-chart-nulls
Oct 30, 2026
Merged

yxxhero merged 1 commit into
databus23:masterfrom
slamdev:drop-chart-nulls

Conversation

@slamdev

@slamdev slamdev commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Since #923, when the chart is unchanged, createPatch builds the desired state of a custom resource as jsonpatch.MergePatch(current, new). A JSON merge patch reads null as "delete this key". Every explicit null a chart renders therefore shows as a removal under --three-way-merge, even though nothing has drifted: the chart is unchanged, so the upgrade sends no patch for the field, and the live object keeps it.

This is common with charts that build a CR spec from defaults files. For example, grafana/k8s-monitoring 3.8.5 renders its Alloy CRs with configMap.key: null, image.digest: null, podDisruptionBudget.minAvailable: null and more. Every plan then shows on each collector:

monitoring, k8s-monitoring-alloy-logs, Alloy (collectors.grafana.com) has changed:
-       key: null
-       name: null
-     digest: null
-     terminationGracePeriodSeconds: null
...

Fix

In the chart-unchanged path, drop the null-valued keys from the new manifest's objects before the merge. A null the chart renders leaves the field unset, so the live value, null or otherwise, is kept. Lists are left as they are, since a merge patch replaces them whole.

Tests

Two cases in TestCreatePatchForUnstructured, both with the chart unchanged:

  • a null in both manifests and stored live → no change
  • a null in both manifests where live has a value → no change

go test ./... passes. I also checked against a live cluster: the Alloy diffs above are gone, and a real manual edit on a PrometheusRule is still reported.

A related gap I left out of scope: a null inside a list element still reads as a change, because MergePatch prunes nulls inside the patch's lists as well.

…y merge

When the chart is unchanged, the desired state of a custom resource is built
by applying the new manifest to the live object as a JSON merge patch. A merge
patch reads a null as "delete this key", so every null a chart renders, such as
an unset value from its defaults, showed as a removal even though the upgrade
sends no patch for it and the live object keeps it. Drop the new manifest's
nulls before the merge.

Signed-off-by: Valentin Fedoskin <valentin.fedoskin@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the custom-resource null-handling changes.

No open issues 🎉

What changed in this PR

This PR refines three-way merge handling for custom resources so unchanged chart-rendered nulls are not reported as drift, while preserving live values.

Changes:

  • Remove null-valued object keys before building the desired state when the chart is unchanged; leave lists untouched.
  • Add regression tests for chart-rendered nulls where the live field contains either null or a value.
File Description
manifest/​generate.go Adds recursive null-key removal to the chart-unchanged merge path.
manifest/​generate_test.go Adds two regression cases covering preservation of live fields.

Review effort: Balanced

@yxxhero
yxxhero merged commit b1c61c1 into databus23:master Oct 30, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants