Skip to content

feat(self-managed): record the installed stack version in-cluster, and let the 1.0 train ship it - #2012

Merged
sbaum1994 merged 3 commits into
release-deploy/stacks/self-managed/v1.0from
kpathak/seed-stack-1.0-upgrade-policy
Sep 22, 2026
Merged

sbaum1994 merged 3 commits into
release-deploy/stacks/self-managed/v1.0from
kpathak/seed-stack-1.0-upgrade-policy

Conversation

@kristinapathak

@kristinapathak kristinapathak commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Ships the upgrade receipt on the 1.0 line. Closes #1977.

Two commits: the first makes this branch capable of publishing at all, the second is the receipt itself.

The receipt

An upgrade has to know where it is starting from, and nothing in a cluster carries that today. Helm tracks chart versions per release; helmfile has no concept of the bundle's own version. So every cluster looks identical at upgrade time and a gate has no basis to decide anything.

This writes a nvcf-upgrade-receipt ConfigMap naming the installed stack version. Only the writer ships here. The validating hook belongs in the release that first has something to validate against, and a writer cannot block an upgrade whereas a checker that misjudges can break every existing customer.

Shipping it on 1.0 is the whole point: a cluster reaching 2.0.0 with no receipt is indistinguishable from a 0.x install, which leaves a gate choosing between refusing everybody and checking nothing. Once any 1.x records a version, absence becomes a refusal that names a version the customer can actually install.

Three decisions worth review:

  • post-install,post-upgrade, not pre-*. A receipt must not claim a version before that version has been applied. post-upgrade alone would skip the first cluster to receive this chart, since Helm runs post-install where no prior release exists — precisely the clusters this exists for.
  • A new last-numbered stage, not a needs: edge. Under the helmfile version this stack pins, needs: places a release in a later DAG layer where it waits on every peer in the previous one, and one slow or failed peer silently skips it — see the admin-issuer-proxy comment in 02-core.yaml.gotmpl. A stage boundary gets the ordering without that failure mode.
  • The chart is local to the bundle, not pulled from the registry, so the receipt works the moment this merges rather than waiting on a chart publish.

Why the first commit exists

The v1.0.0 tag predates release branching: no release_branch_only, no version_file, no publish_release_branch_release, and test-github-release.py asserted no stack may carry a VERSION file. A branch cut from that tag cannot publish anything. It is a carve of 729fcc5 (#1959) and a00ee1e (#1963) reduced to what makes a release possible — documentation changes left out, since a maintenance branch describing capabilities it does not carry is worse than one that says nothing.

All three VERSION files are present because test_the_stacks_release_from_a_branch_and_nothing_else_does validates one for every stack registered as release_branch_only. Verified by removal: dropping the other two fails the suite.

This cuts 1.0.1

Verified by running the real release logic on a correctly-named branch:

Package version: 1.0.1
Source: VERSION release branch

That is the intent — the receipt is the first change to what the stack actually deploys, so it is what publishes the patch.

Test plan

  • make test in deploy/stacks/self-managed — full suite, exit 0, including the new upgrade-receipt-wiring
  • python3 tools/ci/test-github-release.py — 98 tests, OK
  • Rendered the stage: INSTALLED_STACK_VERSION resolves to 1.0.0 from the bundle's own VERSION
  • Release logic dry-run confirms 1.0.1
  • Install on k3d and confirm the ConfigMap appears with the right version, then upgrade and confirm it advances

Note: CodeRabbit reports Review skipped: reviews are disabled for this base branch, so this PR gets no automated review.

🤖 Generated with Claude Code

@kristinapathak
kristinapathak requested review from a team as code owners September 21, 2026 22:15
@kristinapathak kristinapathak added the cicd Continuous integration, build, and release pipeline work label Sep 21, 2026
@kristinapathak kristinapathak added the cicd Continuous integration, build, and release pipeline work label Sep 21, 2026
@kristinapathak kristinapathak self-assigned this Sep 21, 2026
@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 57eca69a-6443-4642-82a8-b1688e0a22c7

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

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

@kristinapathak kristinapathak changed the title ci(release): seed the self-managed 1.0 maintenance branch and backport the upgrade-policy check ci(release): backport release branching to the 1.0 train and add the upgrade-policy check Sep 21, 2026
@kristinapathak
kristinapathak force-pushed the kpathak/seed-stack-1.0-upgrade-policy branch from 77a4539 to 207955c Compare September 21, 2026 22:25
@kristinapathak
kristinapathak requested review from a team as code owners September 21, 2026 22:25
@github-actions

Copy link
Copy Markdown
Contributor

@sanjay-saxena sanjay-saxena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

The v1.0.0 tag this branch is rooted at predates release branching. At that
commit there is no release_branch_only, no version_file, no
publish_release_branch_release and no branch_cut, and test-github-release.py
actively asserted that no stack may carry a VERSION file: the three stacks
still released from main under semantic-release. A branch cut from that tag
therefore has no code path that can publish 1.0.1 at all.

This is a carve of two commits from main, reduced to what makes a release
possible and nothing else:

  729fcc5 ci(release): put the deploy/stacks subprojects back on release branching (#1959)
  a00ee1e fix(release): do not cut a stack patch for a push that changes nothing it ships (#1963)

Their documentation changes are left out; a maintenance branch describing
capabilities it does not carry is worse than one that says nothing. The second
commit is load-bearing rather than incidental: it is what makes
stack_content_changed_since discard the version file, so release bookkeeping
on this branch does not cut versions.

All three VERSION files are present because test_the_stacks_release_from_a_
branch_and_nothing_else_does validates one for every stack registered as
release_branch_only, so the metadata and the files have to agree. They read
1.0.0 because all three stacks were tagged v1.0.0 together; the 0.21.0 seed
#1959 carries is the pre-1.0 value #1964 later had to correct on main.

Nothing here cuts a version. The only path under deploy/stacks that changes is
the version file, which is excluded from the content comparison.

Refs #1977
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kristinapathak
kristinapathak force-pushed the kpathak/seed-stack-1.0-upgrade-policy branch from c223ede to 4c95235 Compare September 21, 2026 23:00
@kristinapathak kristinapathak changed the title ci(release): backport release branching to the 1.0 train and add the upgrade-policy check ci(release): give the 1.0 train the machinery to cut a patch Sep 21, 2026
An upgrade has to know where it is starting from, and nothing in a cluster
carries that today. Helm tracks chart versions per release; helmfile has no
concept of the bundle's own version. So every cluster looks identical at
upgrade time, and a gate has no basis to decide whether the jump it has been
asked to make is one it can make safely.

This writes a nvcf-upgrade-receipt ConfigMap naming the stack version that was
installed. Only the writer ships here. The validating hook belongs in the
release that first has something to validate against, and a writer cannot
block an upgrade whereas a checker that misjudges can break every existing
customer.

Shipping it on the 1.0 line is the point. A cluster that reaches 2.0.0 with no
receipt is indistinguishable from a 0.x install, which leaves a gate choosing
between refusing everybody and checking nothing. Once any 1.x records a
version, absence becomes a refusal that names a version the customer can
actually install.

The hook is post-install and post-upgrade, not pre-*: a receipt must not claim
a version before that version has been applied. post-upgrade alone would skip
the first cluster to receive this chart, because Helm runs post-install where
no prior release exists -- precisely the clusters this exists for.

Ordering is a stage boundary rather than a needs: edge. Under the helmfile
version this stack pins, needs: places a release in a later DAG layer where it
waits on every peer in the previous one, and one slow or failed peer silently
skips it; see the admin-issuer-proxy comment in 02-core.yaml.gotmpl. A new
last-numbered stage gets the ordering without that failure mode.

The chart is local to the bundle rather than pulled from the registry, so the
receipt works the moment this merges instead of waiting on a chart publish.

Closes #1977
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kristinapathak kristinapathak changed the title ci(release): give the 1.0 train the machinery to cut a patch feat(self-managed): record the installed stack version in-cluster, and let the 1.0 train ship it Sep 21, 2026

@sanjay-saxena sanjay-saxena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

The Role granted get, create and patch on every ConfigMap in the namespace,
so anything able to run a pod under this ServiceAccount could rewrite
unrelated ConfigMaps for as long as the binding existed. The Job only ever
touches one object.

get and patch now name that object through resourceNames. create stays
unscoped in a rule of its own, because RBAC matches resourceNames against an
object that does not exist yet and so never satisfies a create rule that names
one; keeping it separate leaves the unscoped verb visible rather than buried
alongside the scoped ones.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sbaum1994
sbaum1994 merged commit 67c010f into release-deploy/stacks/self-managed/v1.0 Sep 22, 2026
17 checks passed
@sbaum1994
sbaum1994 deleted the kpathak/seed-stack-1.0-upgrade-policy branch September 22, 2026 04:17
@balajinvda

Copy link
Copy Markdown
Contributor

This PR is included in version 1.0.1.

The release is available on GitHub release.

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

Labels

cicd Continuous integration, build, and release pipeline work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants