fix: omit null/empty labels and annotations keys from diffs (#1064) - #1068
Merged
Conversation
Charts commonly render a bare `labels:` (null) key when a conditional labels block is empty. deleteStatusAndTidyMetadata only pruned map-typed values, so a null labels/annotations key survived pruning and showed up as a confusing "- labels:" diff entry against the other side that has no such key. pruneNestedMap now removes the key when its value is null, in addition to removing it when the pruned map ends up empty. Also adds a CI reproduction script exercising no-labels charts, dropped label chart versions, bare/null labels blocks, custom resources, flux-style labels, --take-ownership, --dry-run=server and empty labels maps against a real kind cluster. Fixes #1064 Signed-off-by: yxxhero <aiopsclub@163.com>
…fests
Kubernetes treats a null or empty labels/annotations map exactly like an
absent key, but charts frequently render the empty key anyway (e.g. via
a conditional labels block that renders a bare "labels:" line).
A raw textual diff between such a manifest and one that omits the key
reports a meaningless change:
metadata:
finalizers:
- externalsecrets.external-secrets.io/externalsecret-cleanup
- labels:
name: webhook-token
This is the confusion reported in #1064: after the managed-by label is
pruned, the leftover empty key still shows as "- labels:".
stripEmptyMetadataKeys now drops "labels:"/"annotations:" lines from
each parsed manifest whenever the parsed value is null or an empty
mapping. The removal is line-based so surrounding text keeps its
original formatting (# Source comments, key order, indentation); the
parsed document is consulted only to confirm the key really carries no
content, so keys with entries (including nested spec.template.metadata
labels) are untouched.
The CI reproduction script now asserts that the plain diffs for the
bare-null-labels and empty-flow-map-labels chart variants stay clean.
Fixes #1064
Signed-off-by: yxxhero <aiopsclub@163.com>
Issue reproduction tests now live in scripts/issues/<issue-number>.sh with a shared helper library (scripts/issues/lib.sh) and a unified runner: scripts/issues/run.sh # run all issue tests scripts/issues/run.sh 1064 # run a specific issue test Each test fails when its issue is reproduced and streams the full diff output so CI logs show the actual behavior. The CI integration job now invokes the runner instead of a single-purpose script. Signed-off-by: yxxhero <aiopsclub@163.com>
yxxhero
added a commit
to helmfile/helmfile
that referenced
this pull request
Sep 11, 2026
helm-diff v3.15.13 omits null/empty labels and annotations keys from diffs (databus23/helm-diff#1068), which broke the suppress-output-line- regex integration test: the ValidatingWebhookConfiguration manifest no longer renders a bare 'annotations:' key under metadata. Add new golden files suffixed -after-helm-diff-3.15.13 (helm3, helm4, live variants) and select them via version_ge in the test case, keeping the existing 3.11.0 and pre-3.11.0 goldens for older plugin versions. Signed-off-by: yxxhero <aiopsclub@163.com>
yxxhero
added a commit
to helmfile/helmfile
that referenced
this pull request
Sep 11, 2026
* build(deps): bump helm-diff to v3.15.13 Update helm-diff plugin version from v3.15.12 to v3.15.13 across Dockerfiles, recommended version constant, CI matrix, and integration test default. Signed-off-by: yxxhero <aiopsclub@163.com> * fix(ci): add golden files for helm-diff >= 3.15.13 helm-diff v3.15.13 omits null/empty labels and annotations keys from diffs (databus23/helm-diff#1068), which broke the suppress-output-line- regex integration test: the ValidatingWebhookConfiguration manifest no longer renders a bare 'annotations:' key under metadata. Add new golden files suffixed -after-helm-diff-3.15.13 (helm3, helm4, live variants) and select them via version_ge in the test case, keeping the existing 3.11.0 and pre-3.11.0 goldens for older plugin versions. Signed-off-by: yxxhero <aiopsclub@163.com> * refactor(test): drop legacy helm-diff golden files CI only exercises the recommended helm-diff version, so replace the version-suffixed golden files (-after-helm-diff-3.11.0, -after-helm-diff-3.15.13) with a single set of base golden files (diff, diff-live, diff-helm4, diff-live-helm4) containing the v3.15.13 output, and remove the now-dead version gating. Also drop the 'install semver' CI step, whose only consumer was the removed gating. Signed-off-by: yxxhero <aiopsclub@163.com> * fix(test): remove helm-diff version gating from golden file selection The previous commit deleted the version-suffixed golden files but the selection gating was accidentally left behind, making the test reference non-existent files for helm-diff >= 3.15.13. Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1064
helm diffshows a confusing labels-only diff entry like:metadata: finalizers: - externalsecrets.external-secrets.io/externalsecret-cleanup - labels: name: webhook-tokenwhen one side of the diff renders an empty
labelskey and the other omits it.Supersedes #1067 (same content, now from an in-repo branch; includes the reworked test layout).
Root cause
Kubernetes treats a null or empty
labels/annotationsmap exactly like an absent key, but charts frequently render the empty key anyway, e.g. via a conditional block:Two gaps left that noise in the diff:
manifest.Parsekept the raw text of both sides untouched for the plain (text) diff, so a stored manifest containing a barelabels:(null) orlabels: {}produced a meaningless- labels:entry against a new chart revision that omits the key. This was confirmed live in CI: a release installed from a chart with a barelabels:block, diffed against a chart version without it, printed exactly the- labels:line from the issue.deleteStatusAndTidyMetadata/pruneNestedMap(live-object tidy used by--three-way-mergeand--take-ownership) only handled map-typed values, so a null"labels": nullvalue survived pruning with the same effect.Fix
stripEmptyMetadataKeysdropslabels:/annotations:lines from each parsed manifest whenever the parsed value is null or an empty mapping. The removal is line-based so surrounding text keeps its original formatting (# Sourcecomments, key order, indentation); the parsed document is only consulted to confirm the key really carries no content, so keys with entries (including nestedspec.template.metadata.labels) are untouched.pruneNestedMapremoves the key when its value is null, in addition to the existing removal when the pruned map ends up empty.Testing
{}labels lines are stripped byParsewhile labels with content and nested pod-template labels are kept.scripts/issues/<issue-number>.shwith a shared helper library and a unified runner (scripts/issues/run.sh, runs all or specific issues). The CI integration job runs them against the kind cluster. Testscripts/issues/1064.shcovers (plain + three-way-merge):managed-bylabel, diffed against a chart version that dropped itlabels:(null) key vs a version without the block — the exact Bug: helm-diff shows- labels, if resource contains onlyapp.kubernetes.io/managed-bylabel #1064 symptom (- labels:)--take-ownership--dry-run=serverlabels: {}map — produces- labels: {}without the fixSigned-off-by: yxxhero aiopsclub@163.com