Skip to content

fix(bundler): the helm readiness gate blocks and re-runs on upgrade - #2863

Open
EronWright wants to merge 6 commits into
NVIDIA:mainfrom
turfbuild:fix/readiness-gate-hooks-stripped
Open

EronWright wants to merge 6 commits into
NVIDIA:mainfrom
turfbuild:fix/readiness-gate-hooks-stripped

Conversation

@EronWright

Copy link
Copy Markdown

Summary

The readiness gate is emitted without its helm.sh/hook annotations, so under --deployer helm it neither blocks the deploy nor re-runs on upgrade. This keeps them.

Motivation / Context

gatemanifest.Render emits helm.sh/hook: post-install,post-upgrade and helm.sh/hook-delete-policy: before-hook-creation for --deployer helm. localformat's stripHelmHooks, applied to every folder it writes, removes them again — the gate Job ships with annotations: {}.

A Job blocks helm upgrade if it is a hook (--wait) or if Helm is told to wait for a bare one (--wait-for-jobs). deploy.sh drops --wait-for-jobs because the gate is a hook; the gate is not a hook, because the annotations were stripped. The bundle has neither mechanism.

So the gate does not block — dependents install against a cluster it has not finished checking — and does not re-run, since a Job's spec.template is immutable and nothing deletes it. helm upgrade reports success either way.

Not a regression: byte-identical output at #1110, where the gate was introduced, and at current main. #1110's notes describe this path as --wait --wait-for-jobs, but the merged template only ever passed --wait.

Fixes: #2862
Related: #904, #2408 (the same failure, fixed for Argo CD only)

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/)

Implementation Notes

Keyed on the injection phase, not the deployer. localformat/doc.go invariant 1 forbids per-deployer branches in Write, and this adds none: the readiness folder is the only folder localformat writes whose content its own caller authored. Recipe content, whose author cannot know the target deployer, is stripped exactly as before.

Not a blanket exemption for non-Argo deployers. That reopens #1835 — 31 recipe files carry 96 hook annotations including before-hook-creation, which under plain Helm made resources fire-and-forget, skipped by upgrade, left behind by uninstall, and delete-recreated on every upgrade.

The exempt path still round-trips the YAML. stripHelmHooks also re-encodes at a consistent indent (what keeps goldens stable) and rejects unparseable input (the only YAML check a manifest gets before a cluster sees it). The round-trip moves into a shared rewriteYAMLDocs; the exemption skips the filter, never the rewrite — which is why Argo CD's bytes do not move.

Testing

make lint      # 0 issues
go test ./...  # all pass

The new golden catches the bug. TestBundleGolden_ReadinessGate fails with the fix reverted and passes with it, and pins both annotations by name as well as by golden bytes. pkg/bundler/deployer/helm/testdata/ previously had no readiness golden at all, which is why nothing caught this.

The Argo goldens must not move — they don't. git diff --exit-code over argocd/testdata and argocdhelm/testdata is clean and TestBundleGolden* pass unchanged; they are not -updated here.

The exemption is phase-scoped. TestWrite_HookExemptionIsScopedToReadiness puts a readiness folder beside a hook-annotated post folder and asserts the gate keeps its annotations and the post wrapper is still stripped. A blanket exemption passes the first half and reopens #1835.

Coverage: localformat 81.9%, helm 88.1%. normalizeYAMLDocs and stripHelmHooks are at 100%.

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert
  • Medium — Touches multiple components or has broader impact
  • High — Breaking change, affects critical paths, or complex rollout

Rollout notes: the only output that changes is the readiness folder of a --deployer helm --readiness-hooks bundle, which regains two annotations it was always meant to ship with. Argo CD output is byte-identical; the Flux and Helmfile gates carry no annotations to keep. No flags, config or API surface.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed — no user-facing docs describe the gate's annotations
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

gatemanifest.Render picks the readiness gate's annotations with full knowledge
of the target deployer: Helm hooks for helm, argocd.argoproj.io/sync-options
for Argo CD, and -- documented in its own comment -- deliberately no
helm.sh/hook for Argo, because Argo maps one to a PostSync hook that never
fires under syncPolicy.automated. writeLocalHelmFolder then strips the Helm
ones back off on the way to disk.

Under --deployer helm that leaves the gate shipping as a bare Job, and both
annotations it lost were load-bearing:

  - helm.sh/hook: post-install,post-upgrade is what makes the gate BLOCK.
    deploy.sh passes --wait without --wait-for-jobs, and says why in its own
    comment: "Helm --wait blocks on hook completion regardless of
    --wait-for-jobs". True of a hook. A bare Job under --wait alone returns as
    soon as the object exists, so dependents start against a cluster the gate
    has not finished checking -- which is the entire thing a gate exists to
    prevent.

  - helm.sh/hook-delete-policy: before-hook-creation is what makes it RE-RUN.
    A Job's spec.template is immutable, so an identical manifest is a no-op
    patch. Without the delete-and-recreate the gate asserts once, at install,
    and every subsequent upgrade ships unverified.

There is no golden for a helm readiness bundle, which is why this has been
invisible. This adds one.

The fix is keyed on the PHASE, not on the deployer -- localformat gains no
deployer knowledge, and the invariant in its doc.go, that it never grows
per-deployer branches, still holds. The readiness folder is simply the only
folder localformat writes that localformat's caller authored: every other one
carries recipe content whose author cannot know the consuming deployer, and
stripping those stays exactly as it was. That distinction is not cosmetic --
31 recipe files carry hook annotations whose mandated trio includes
before-hook-creation, and doc.go records what keeping those did under plain
Helm before NVIDIA#1835.

Mechanically: the yaml.v3 round-trip moves out of stripHelmHooks into a shared
rewriteYAMLDocs, and normalizeYAMLDocs exposes it without the hook filter. The
round-trip is not incidental -- it is the only YAML-validity check a rendered
manifest gets, and the consistent re-encode is what keeps every golden stable
-- so the exempt path skips the filter, never the rewrite.

That is also what makes this a no-op for Argo CD: its gate carries
sync-options, which the filter never touched, so normalize-only reproduces the
same bytes. git diff --exit-code over the argocd and argocd-helm testdata is
empty, and is the regression proof.

Terraform's gate is separate: it is annotation-free by construction and does
not change here.

Signed-off-by: Eron Wright <eronwright@gmail.com>
@EronWright
EronWright requested a review from a team as a code owner September 19, 2026 19:21
@copy-pr-bot

copy-pr-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

Copy link
Copy Markdown
Contributor

Welcome to AICR, @EronWright! Thanks for your first pull request.

Before review, please ensure:

  • All commits are signed off per the DCO
  • CI checks pass (tests, lint, security scan)
  • The PR description explains the why behind your changes

A maintainer will review this soon.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

Configuration used: Repository: NVIDIA/aicr/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 29263cd2-36bb-40c8-940c-0b6585c41ae1

📥 Commits

Reviewing files that changed from the base of the PR and between 3fff3e9 and 964b12a.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/aicr/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 302cf8d4-ed8b-4f21-a75f-4fa4d5870afe

📥 Commits

Reviewing files that changed from the base of the PR and between b99c8e4 and d048752.

📒 Files selected for processing (17)
  • pkg/bundler/deployer/helm/helm_test.go
  • pkg/bundler/deployer/helm/testdata/readiness_gate/001-foo/cluster-values.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/001-foo/install.sh
  • pkg/bundler/deployer/helm/testdata/readiness_gate/001-foo/upstream.env
  • pkg/bundler/deployer/helm/testdata/readiness_gate/001-foo/values.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/002-foo-readiness/Chart.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/002-foo-readiness/cluster-values.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/002-foo-readiness/install.sh
  • pkg/bundler/deployer/helm/testdata/readiness_gate/002-foo-readiness/templates/readiness.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/002-foo-readiness/values.yaml
  • pkg/bundler/deployer/helm/testdata/readiness_gate/README.md
  • pkg/bundler/deployer/helm/testdata/readiness_gate/deploy.sh
  • pkg/bundler/deployer/localformat/empty_render_test.go
  • pkg/bundler/deployer/localformat/hooks.go
  • pkg/bundler/deployer/localformat/local_helm.go
  • pkg/bundler/deployer/localformat/writer.go
  • pkg/bundler/deployer/localformat/writer_test.go

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Walkthrough

The local Helm writer now preserves Helm hook annotations for readiness folders while continuing to strip them from pre and post folders. YAML rewriting is shared between normalization and hook removal. New readiness-gate Helm fixtures include charts, deployment scripts, RBAC, a hook Job, and documentation. Tests verify phase-scoped annotation handling and compare the generated bundle with a golden fixture.

Priority: ⬆️ High

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: High

Suggested reviewers: lockwobr

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Helm readiness gate bug and its blocking and upgrade behavior.
Description check ✅ Passed The description directly explains the missing Helm hook annotations, the scoped implementation, tests, and expected behavior.
Linked Issues check ✅ Passed The change satisfies the coding requirements in #2862. injectAuxiliaryFolder keeps annotations only for phaseReadiness. writeLocalHelmFolder still strips Helm hooks for other folders and perform…
Out of Scope Changes check ✅ Passed The changes stay within #2862. The new normalization helper, readiness Helm fixture, golden output, deployment files, and tests support verification of the readiness bundle. The implementation keeps h…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Recipe evidence check

No leaf overlays affected by this PR.

This gate is warning-only and never blocks merge.

mchmarny
mchmarny previously approved these changes Sep 20, 2026

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approve: no findings against d048752. Required checks and focused localformat/Helm tests pass at the reviewed SHA.

@mchmarny
mchmarny enabled auto-merge (squash) September 20, 2026 12:05
@mchmarny
mchmarny dismissed their stale review September 20, 2026 13:54

Withdrawing approval because current-head CI deterministically fails TestBundleGolden_ReadinessGate after #2855 changed the generated install script; see the follow-up comment for the required golden refresh.

@mchmarny

Copy link
Copy Markdown
Member

I removed my approval because the current-head CI failure is deterministic and needs a branch update.

The new readiness-gate golden was generated before #2855 changed install-upstream-helm.sh.tmpl. The generator now adds the dry-run stale-archive guard and its explanatory comment, but pkg/bundler/deployer/helm/testdata/readiness_gate/001-foo/install.sh still contains the old unconditional .aicr-chart.tgz check. Consequently, TestBundleGolden_ReadinessGate fails its generated-versus-golden comparison; the final gate failure only propagates that result.

The PR was green at d048752 and 8f230e7, before #2855's b510a0f6 entered the branch, and has failed with this same mismatch from 085f316 onward. Please regenerate or update the readiness-gate golden against current main, then verify with:

GOFLAGS=-mod=readonly go test ./pkg/bundler/deployer/helm -run '^TestBundleGolden_ReadinessGate$' -count=1

Failing test job

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: readiness gate is broken with --deployer helm due to missing hook annotations

2 participants