Skip to content

fix: recognize an already-purged Helm release as finalizer success - #28

Merged
williamsena-vtex merged 1 commit into
mainfrom
fix/helm-release-not-found-finalizer
Sep 4, 2026
Merged

fix: recognize an already-purged Helm release as finalizer success#28
williamsena-vtex merged 1 commit into
mainfrom
fix/helm-release-not-found-finalizer

Conversation

@williamsena-vtex

Copy link
Copy Markdown
Contributor

Summary

  • helmReleaseFinalizer's errors.Is(err, driver.ErrReleaseNotFound) check only matches when Uninstall.Run fails on the initial lookup. When the release disappears partway through instead, Helm wraps the sentinel in a flattened errors.Errorf(...) (uninstall.go:163) that errors.Is can't see through — that failure mode retried the finalizer forever instead of treating "nothing left to clean up" as success.
  • Fix: check release existence upfront via action.Get, plus a text-match fallback (isReleaseNotFoundErr) on Uninstall's own error for the same race in the narrow window between the two calls.
  • Also fixes two related NotFound gaps found while in this code path: handleUpdateErr now swallows NotFound (object already deleted elsewhere) instead of surfacing it as a reconcile error, and the final r.Delete(ctx, cTTL) on the happy path now ignores NotFound like the initial r.Get already did.

Why

Root-caused from a production log on trx:

error: uninstallation completed with 1 error(s): uninstall: Failed to purge the release: release: not found

sfj-2f40163--tupan was stuck on exactly this error, permanently blocking that ConditionalTTL's deletion (and any finalizers queued after the release one) while the tupan account accumulated 94 active Knative Revisions.

Test plan

  • go build ./..., go vet ./...
  • go test ./controllers/... — new tests use Helm's in-memory storage driver + kubefake.PrintingKubeClient (no real cluster/Helm release touched, same fixture pattern as Helm's own unit tests):
    • TestIsReleaseNotFoundErr — sentinel, Helm's flattened wrapping, and an unrelated error
    • TestHelmReleaseFinalizer_AlreadyGone — reproduces the tupan/trx scenario directly: a release that was never created must not block the finalizer
    • TestHelmReleaseFinalizer_NoopWhenHelmSpecNil / _NoopWhenDeleteFalse — existing early-return paths, unchanged
    • TestHandleUpdateErr updated for the new NotFound-swallowing behavior, plus a new case confirming a genuinely unrelated error still propagates
  • envtest suite (TestAPIs) not run here — no KUBEBUILDER_ASSETS in this environment; CI should cover it
  • Validate on trx that sfj-2f40163--tupan (and any other sfj-*--tupan objects stuck the same way) finish deleting after this ships

helmReleaseFinalizer's errors.Is(err, driver.ErrReleaseNotFound) check
only matches when Uninstall.Run fails immediately on the initial
lookup. When the release disappears partway through instead -
resources already deleted, then the purge step finds no release
record left - Helm wraps the sentinel in a new, flattened
errors.Errorf("uninstallation completed with N error(s): ...")
(helm.sh/helm/v3/pkg/action/uninstall.go:163) that breaks errors.Is.
That specific failure mode retried the finalizer forever instead of
recognizing there was nothing left to clean up, permanently blocking
the ConditionalTTL's deletion and any finalizers still pending after
it.

Root-caused this from a production log on trx: sfj-2f40163--tupan
stuck on exactly this error while the tupan account accumulated 94
active Knative Revisions.

Fix: check release existence upfront via action.Get before calling
Uninstall, plus a text-match fallback on Uninstall's own error for the
same race in the narrow window between the two calls.

Also fixes two related NotFound gaps found while in this code path:
handleUpdateErr now swallows NotFound (the object was already deleted
by something else) instead of surfacing it as a reconcile error, and
the final r.Delete(ctx, cTTL) on the happy path now ignores NotFound
the same way the initial r.Get already did.
@vtex-pr-sentinel

Copy link
Copy Markdown

🛡️ SDD Check — action required

I couldn't detect an SDD in this PR. Please check one option below (requires write access to the repo):

  • SDD lives in another PR — paste the SDD PR URL here:
  • This PR doesn't need an SDD
  • SDD applies, but I'm not adopting it in this PR

@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

@AdrianWR AdrianWR left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Merge Readiness and Risk Assessment

Status: Ready to merge pending review approval.

Code quality: High. The solution is defensive (belt-and-suspenders approach), well-documented, and handles the exact production failure mode.
CI: All 4 checks passing (SonarCloud, Arnica Code Risks, SDD Check, ECR publish).
Test coverage: Solid unit tests with realistic fixtures; envtest suite deferred to CI (author notes no KUBEBUILDER_ASSETS in local environment).
Risk level: Low. Changes are localized to the Helm release finalizer and final deletion path. The upfront Get() is defensive and doesn't change behavior for the happy path. The text-match fallback is conservative—only triggers when errors.Is fails, which only happens with Helm's specific flattening behavior.
Production impact: Fixes a stuck finalizer in production (sfj-2f40163--tupan on trx), allowing 94+ blocked Knative Revisions to be cleaned up.

@williamsena-vtex
williamsena-vtex merged commit f0ccb64 into main Sep 4, 2026
3 of 4 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