Skip to content

fix: drop null-valued labels/annotations keys in metadata tidy - #1067

Closed
yxxhero wants to merge 2 commits into
databus23:masterfrom
yxxhero:fix-1064-empty-labels
Closed

fix: drop null-valued labels/annotations keys in metadata tidy#1067
yxxhero wants to merge 2 commits into
databus23:masterfrom
yxxhero:fix-1064-empty-labels

Conversation

@yxxhero

@yxxhero yxxhero commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1064

helm diff shows a confusing labels-only diff entry like:

  metadata:
    finalizers:
    - externalsecrets.external-secrets.io/externalsecret-cleanup
-   labels:
    name: webhook-token

when one side of the diff renders an empty labels key and the other omits it.

Root cause

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 block:

metadata:
  name: webhook-token
  labels:
{{- with .Values.labels }}
{{ toYaml . | indent 4 }}
{{- end }}

Two gaps left that noise in the diff:

  1. manifest.Parse kept the raw text of both sides untouched for the plain (text) diff, so a stored manifest containing a bare labels: (null) or labels: {} produced a meaningless - labels: entry against a new chart revision that omits the key.
  2. deleteStatusAndTidyMetadata/pruneNestedMap (live-object tidy used by --three-way-merge and --take-ownership) only handled map-typed values, so a null "labels": null value survived pruning with the same effect.

Fix

  1. stripEmptyMetadataKeys 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 only consulted to confirm the key really carries no content, so keys with entries (including nested spec.template.metadata.labels) are untouched.
  2. pruneNestedMap removes the key when its value is null, in addition to the existing removal when the pruned map ends up empty.

Testing

  • Unit tests: null labels/annotations keys are removed by the tidy; null/empty/{} labels lines are stripped by Parse while labels with content and nested pod-template labels are kept.
  • New CI integration script scripts/repro-issue-1064.sh runs against the kind cluster in CI and covers (plain + three-way-merge):
    • A: chart resource without any labels
    • B: release installed with an explicit managed-by label, diffed against a chart version that dropped it
    • C: chart rendering a bare labels: (null) key vs a version without the block — the exact Bug: helm-diff shows - labels, if resource contains only app.kubernetes.io/managed-by label #1064 symptom (- labels:)
    • D: custom resource (unstructured/CRD path, like the ExternalSecret in the issue) with finalizers and no labels
    • E: flux-style extra labels on the live object
    • F: --take-ownership
    • G: --dry-run=server
    • H: explicit empty labels: {} map — produces - labels: {} without the fix

Signed-off-by: yxxhero aiopsclub@163.com

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 databus23#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 databus23#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 databus23#1064

Signed-off-by: yxxhero <aiopsclub@163.com>
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.

Bug: helm-diff shows - labels, if resource contains only app.kubernetes.io/managed-by label

1 participant