Skip to content

fix: skip hooks in the --take-ownership ownership check (#782) - #1074

Merged
yxxhero merged 1 commit into
databus23:masterfrom
somaz94:fix/take-ownership-skip-hooks
Sep 17, 2026
Merged

yxxhero merged 1 commit into
databus23:masterfrom
somaz94:fix/take-ownership-skip-hooks

Conversation

@somaz94

@somaz94 somaz94 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

With --take-ownership, checkOwnership visits every rendered resource, hooks included. Helm keeps hooks out of the release manifest and never adds the meta.helm.sh/release-* annotations to them, so every live hook was reported as changed ownership even when nothing changed (the first case in #782). With --detailed-exitcode that also turns a no-op upgrade into exit code 2.

This skips resources whose rendered object has the helm.sh/hook annotation, which is the same check Helm uses to split hooks out of the manifest. It also stops a leftover test hook (e.g. the pod from helm test) from failing the whole diff with failed to parse content of Kubernetes resource.

Tests:

The second case in #782 (a changed hook Job fails with field is immutable) is not fixed here. manifest.Generate three-way patches live hooks like any other resource, and that also happens with plain --three-way-merge. Fixing it means deciding what a hook should be compared against, since Helm recreates hooks instead of patching them. Happy to follow up if you have a preferred direction.

Refs #782

AI disclosure: I used Claude Code while working on this.

@yxxhero yxxhero left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I did a deep pass over this and everything holds together. What I verified beyond the tests:

  • Hook detection matches Helm's own. Helm v4 splits hooks out of the manifest with a key-presence check on the same constant (pkg/release/v1/util/manifest_sorter.go uses entry.Metadata.Annotations[release.HookAnnotation], if !ok -> generic), so _, isHook := accessor.GetAnnotations()[releasev1.HookAnnotation] is exactly the right predicate, and skipping before helper.Get avoids a pointless API round-trip per hook.
  • The annotation survives manifest.Generate. checkOwnership builds its resource list from the post-Generate install manifest, so this only works if helm.sh/hook is still there afterwards. It is: the to-be-created branch marshals the rendered object, the to-be-updated branch marshals the live hook (Helm creates hooks with their template annotations intact), and deleteStatusAndTidyMetadata only prunes meta.helm.sh/release-*, deployment.kubernetes.io/revision and friends. That's a non-obvious invariant — noted inline.
  • No diff coverage is lost. Skipping hooks here doesn't make them invisible: Generate already appends the live hook to the release manifest and the merged hook to the install manifest, so an unchanged hook still diffs as unchanged. The fix only removes the bogus ownership report (and with --detailed-exitcode, the spurious exit code 2).
  • The test-hook crash claim checks out. isHook() compares the annotation with ==, so a live helm.sh/hook: test object is dropped by parseContent, ParseObject then returns failed to parse content of Kubernetes resource and the whole diff used to die on it.
  • CI is green on all legs, including the integration runs that execute scripts/issues/782.sh on kind against Helm v3.18.6 / v3.22.0 / v4.3.0.

On the deferred second case of #782: agreed it shouldn't block this. For the follow-up, one direction that mirrors Helm's recreate-not-patch behavior would be to treat target resources that carry the hook annotation specially in manifest.Generate — take the previous release's hook manifest (from helm get hooks) as the original side and emit the rendered target as the new side, instead of three-way patching the live object. That would also kill the field is immutable failure for Jobs whose template changed. Happy to see it as a separate PR.

Comment thread cmd/upgrade.go
accessor, err := meta.Accessor(info.Object)
if err != nil {
return err
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One invariant worth keeping in mind for the future: this checks the annotation on the object built from the post-manifest.Generate install manifest, not the raw rendered one. That works today because Generate preserves helm.sh/hook in both of its branches (rendered object marshaled for to-be-created, live object for to-be-updated) and deleteStatusAndTidyMetadata only prunes meta.helm.sh/release-* style annotations. If someone ever adds helm.sh/hook to that tidy list, hooks would silently start being reported as ownership changes again — the unit test here would catch it, which is good.

Comment thread cmd/upgrade_test.go
currentSpecs := make(map[string]*manifest.MappingResult)

newOwnedReleases, err := checkOwnership(&diffCmd{release: "rel", namespaces: namespaces{namespace: "default"}}, resources, currentSpecs)
if err != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice regression property: this fails on master in two independent ways — mustNotGet fires the moment checkOwnership fetches the live hook, and the hook entries pollute newOwnedReleases/currentSpecs even if the fetch were allowed. The owned case (same release) and unmanaged case (no release) also pin down that the skip doesn't over-suppress.

Comment thread scripts/issues/782.sh
if grep -q '^Error:' "$WORK/stripped.out"; then
echo "FAIL: scenario [$scenario] helm diff failed (#${ISSUE})"
FAIL=1
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The ^Error: guard is a good call — without it a hard failure would print no ownership lines at all and every not-reported assertion would vacuously pass. Same shape as the guard the 1064 script needs for its noassert baseline.

@yxxhero yxxhero left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving from my side — the fix matches Helm's own hook-splitting semantics, the analysis in my earlier review holds (annotation survives manifest.Generate, no diff coverage lost, test-hook parse failure also addressed), and all CI legs including the kind integration runs of scripts/issues/782.sh are green.

@yxxhero
yxxhero merged commit cd0f9b6 into databus23:master Sep 17, 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.

2 participants