fix(kubernetes): keep tenant addon CRDs when a release is uninstalled - #3586
Aleksei Sviridkin (lexfrei) merged 4 commits into
Conversation
📝 WalkthroughWalkthroughFour Helm CRD charts now add ChangesCRD retention
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/system/vertical-pod-autoscaler-crds/Makefile`:
- Around line 12-19: Update the awk transformation in the Makefile’s update
target to guarantee that each CustomResourceDefinition receives the
helm.sh/resource-policy annotation even when metadata.annotations is absent.
Create the annotations block before adding the policy, or fail before replacing
the generated file if it cannot be inserted; do not allow update to finish with
an unannotated CRD.
- Line 16: Update the curl invocation in the Makefile download target to include
the --fail option, ensuring HTTP 4xx and 5xx responses stop the command before
the generated template is modified by the subsequent awk step.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3d02055f-18c6-495e-90e3-c128aad23a94
📒 Files selected for processing (14)
packages/system/gateway-api-crds/Makefilepackages/system/gateway-api-crds/templates/crds-experimental.yamlpackages/system/gateway-api-crds/tests/resource_policy_test.yamlpackages/system/prometheus-operator-crds/Makefilepackages/system/prometheus-operator-crds/tests/resource_policy_test.yamlpackages/system/prometheus-operator-crds/values.yamlpackages/system/vertical-pod-autoscaler-crds/Makefilepackages/system/vertical-pod-autoscaler-crds/templates/vpa-v1-crd-gen.yamlpackages/system/vertical-pod-autoscaler-crds/tests/resource_policy_test.yamlpackages/system/vsnap-crd/Makefilepackages/system/vsnap-crd/templates/volumesnapshotclasses.yamlpackages/system/vsnap-crd/templates/volumesnapshotcontents.yamlpackages/system/vsnap-crd/templates/volumesnapshots.yamlpackages/system/vsnap-crd/tests/resource_policy_test.yaml
| # The stamp goes into the CRD's existing metadata.annotations block; a CRD that | ||
| # arrives without one is skipped, and the chart's helm-unittest suite is what | ||
| # catches that. | ||
| update: | ||
| curl -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml | ||
| @set -e; f=./templates/vpa-v1-crd-gen.yaml; tmp=$$(mktemp); \ | ||
| awk '/^ helm.sh\/resource-policy: keep$$/{next} /^kind: /{kind=$$2} /^ annotations:$$/ && kind=="CustomResourceDefinition"{print; print " helm.sh/resource-policy: keep"; next} {print}' "$$f" > "$$tmp"; \ | ||
| cat "$$tmp" > "$$f"; rm -f "$$tmp" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Handle CRDs that have no metadata.annotations block.
The awk rule adds the policy only when it finds ^ annotations:$. If upstream removes that block, make update completes with an unannotated CRD. Create the annotations block when absent, or fail the update before replacing the generated file. The separate test target does not run from update.
🧰 Tools
🪛 checkmake (0.3.2)
[warning] 15-15: Required target "all" is missing from the Makefile.
(minphony)
[warning] 15-15: Required target "clean" is missing from the Makefile.
(minphony)
[warning] 15-15: Required target "test" must be declared PHONY.
(minphony)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/system/vertical-pod-autoscaler-crds/Makefile` around lines 12 - 19,
Update the awk transformation in the Makefile’s update target to guarantee that
each CustomResourceDefinition receives the helm.sh/resource-policy annotation
even when metadata.annotations is absent. Create the annotations block before
adding the policy, or fail before replacing the generated file if it cannot be
inserted; do not allow update to finish with an unannotated CRD.
| # arrives without one is skipped, and the chart's helm-unittest suite is what | ||
| # catches that. | ||
| update: | ||
| curl -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Fail on an HTTP error before changing the generated template.
curl accepts HTTP 4xx and 5xx responses without --fail. The following awk command can then replace vpa-v1-crd-gen.yaml with an error page. Add --fail to prevent a failed download from corrupting the tracked template.
Proposed fix
- curl -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml
+ curl --fail -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| curl -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml | |
| curl --fail -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/system/vertical-pod-autoscaler-crds/Makefile` at line 16, Update the
curl invocation in the Makefile download target to include the --fail option,
ensuring HTTP 4xx and 5xx responses stop the command before the generated
template is modified by the subsequent awk step.
|
Reopening to pick up the multus fix on main. |
Andrei Kvapil (kvaps)
left a comment
There was a problem hiding this comment.
Verified the mechanics end to end and they hold. Rendering all four charts, every
CRD carries helm.sh/resource-policy: keep (12 / 2 / 10 / 3) and nothing else
does — the Gateway API ValidatingAdmissionPolicy pair is correctly left out. I
re-ran the stamping awk over the five committed files and it is a no-op, and I ran
the real update: fetch for gateway-api-crds (kubectl kustomize ... ?ref=v1.5.1)
and vsnap-crd: both come back byte-identical to what is committed here.
The tests are not self-asserting. Dropping the annotation from one vsnap CRD turns
two cases red; appending an unannotated CRD to the Gateway API file turns the
wildcard case and the document-count pin red. hack/helm-unit-tests.sh is green
across the whole repo.
On the trade-off: reinstall after a keep-uninstall is safe. Helm excludes kept
resources from the delete set without touching their metadata, so
meta.helm.sh/release-name and -namespace survive; release name and
storageNamespace are fixed in these HelmReleases, so the retry install passes the
ownership check and adopts the CRDs rather than failing on "exists and cannot be
imported". Existing clusters need no migration either — the platform upgrade runs
helm upgrade on these releases and patches the annotation onto the CRDs already in
place. Coverage looks complete for the stated scope: every tenant addon that renders
CRDs from templates/ is now annotated, and the ones that ship CRDs under crds/
(velero, gpu-operator) were never at risk since Helm does not delete those.
Two things worth resolving, both about consistency inside this block of PRs rather
than correctness here.
The stamping step is copy-pasted three times, and #3595 is hardening the fourth
copy in the opposite direction. The same awk one-liner appears verbatim in
gateway-api-crds/Makefile:22, vertical-pod-autoscaler-crds/Makefile:18 and
vsnap-crd/Makefile:23, alongside a fourth, differently-shaped and non-idempotent
variant already in etcd-operator-crds/Makefile:20. #3595 gives that fourth one a
count guard — one annotation per CRD or the update fails — and defers writing
templates/ until after the check. The three added here have neither. A CRD that
arrives without a metadata.annotations block, or with annotations: {} in flow
style, is skipped silently, the step exits 0, and since rm -rf templates has
already run there is nothing to fall back to. The suite in this PR does catch it in
CI, which the description says, but #3595 has decided the guard belongs at the point
of failure. A shared hack/stamp-crd-keep.sh — the kind-aware idempotent matcher
from this PR plus the count guard from #3595 — would leave one implementation
instead of four.
The suites are much heavier than the equivalent in #3590. 387 of the 456 added
lines are tests, and most of that is repetition:
gateway-api-crds/tests/resource_policy_test.yaml:35-141 is twelve near-identical
cases that each re-check what the matchMany case at :18 already covers, and
prometheus-operator-crds/tests/resource_policy_test.yaml:24-142 is ten more. #3590
gets the same guarantee for cert-manager-crds in a single wildcard case. Worth
converging on one shape across the block.
Smaller points:
- The description says a CRD from a later upstream bump is "covered without editing
the test", buthasDocuments: count: 14at :32 (andcount: 2in the VPA suite)
is exactly what will need editing on such a bump. Both properties are worth having;
the sentence just reads as if the pin were not there. - Three of the four suites scope to no template and assert
kind == CustomResourceDefinitionon every document. That is stricter than
intended: a non-CRD resource appearing upstream would fail them for the wrong
reason. The Gateway API suite gets this right withdocumentSelector+matchMany.
Conversely it is the only one with a suite-leveltemplates:(:15), so a second
file undertemplates/would go untested — moot today, sinceupdate:writes
exactly one. renders twelve CRDs and the admission policy pair(:30) bakes the count into the
case name as well as the assertion; the name will go stale first.vsnap-crd/Makefile:22createstmpoutside the loop, so a mid-loop awk failure
leaves it behind.
Out of scope, but the same class exists on the management cluster: kamaji-crds
(tenantcontrolplanes), piraeus-operator-crds, kubevirt-operator,
kubevirt-cdi-operator, metallb, objectstorage-controller,
mariadb-operator-crds, kubeovn and external-secrets all render CRDs from
templates/ without the annotation. Not urgent — those HelmReleases set no
install.remediation, and the default there is to perform no action — but the same
protection would cost the same.
CI is red on "Unit & controller tests" for an unrelated reason: the EXIT-trap freeze
guard in hack/cozyreport.bats:2553 does not list
hack/run-kubernetes-talos-diagnostics_test.bats, which landed on main with #3548.
This PR touches no .bats file.
Approving — none of the above blocks the fix.
The tenant addon HelmRelease remediates a failed install by uninstalling the release, and an uninstall deletes whatever the chart rendered. Deleting a CRD makes the API server garbage-collect every custom resource of that kind, so a failed reinstall takes every Gateway, HTTPRoute and ReferenceGrant in the tenant cluster with it. Stamp helm.sh/resource-policy: keep on each CustomResourceDefinition and teach `make update` to reapply it, since that target rebuilds templates/ from upstream and would otherwise drop the annotation silently. The ValidatingAdmissionPolicy pair the experimental kustomization also emits holds no user data and stays unannotated. Removing the release on purpose now leaves the definitions behind to be deleted by hand. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
The tenant addon HelmRelease remediates a failed install by uninstalling the release, and an uninstall deletes whatever the chart rendered. Deleting a CRD makes the API server garbage-collect every custom resource of that kind, so a failed reinstall takes every VerticalPodAutoscaler in the tenant cluster with it, along with the recommendation history held in the checkpoints. Stamp helm.sh/resource-policy: keep on each CustomResourceDefinition and teach `make update` to reapply it, since that target refetches the manifest from upstream and would otherwise drop the annotation silently. Removing the release on purpose now leaves the definitions behind to be deleted by hand. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
The tenant addon HelmRelease remediates a failed install by uninstalling the release, and an uninstall deletes whatever the chart rendered. Deleting a CRD makes the API server garbage-collect every custom resource of that kind, so a failed reinstall takes every VolumeSnapshot and VolumeSnapshotContent in the tenant cluster with it — the objects that carry the only reference to a snapshot on the storage backend. Stamp helm.sh/resource-policy: keep on each CustomResourceDefinition and teach `make update` to reapply it, since that target rebuilds templates/ from upstream and would otherwise drop the annotation silently. Removing the release on purpose now leaves the definitions behind to be deleted by hand. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
The tenant addon HelmRelease remediates a failed install by uninstalling the release, and an uninstall deletes whatever the chart rendered. Deleting a CRD makes the API server garbage-collect every custom resource of that kind, so a failed reinstall takes every ServiceMonitor, PodMonitor, PrometheusRule and ScrapeConfig in the tenant cluster with it. The CRDs ship inside a vendored subchart that `make update` re-pulls, so the annotation is set through the chart values the subchart already renders rather than by editing templates that would be overwritten. Removing the release on purpose now leaves the definitions behind to be deleted by hand. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
5f646c8 to
d90a6a5
Compare
## What this PR does `make update` in `packages/system/etcd-operator-crds` vendors three CRDs and stamps `helm.sh/resource-policy: keep` onto each with an awk step anchored on the `controller-gen.kubebuilder.io/version:` line. That line is upstream's to emit. If a future controller-gen stops writing it, the awk matches nothing, writes the CRD out unstamped and exits zero, so the regeneration reports success and the CRDs land without the annotation that keeps a chart uninstall from deleting them, and with them every EtcdCluster, EtcdMember and EtcdSnapshot in the cluster. The step now counts what it produced and requires exactly one annotation per CRD. Zero means the anchor is gone; more than one means it matched somewhere unintended. Every CRD is staged in a temporary directory and `templates/` is replaced only after all of them have been counted, so a failure on any one leaves the vendored files exactly as they were rather than wiping the directory and refilling part of the set. I checked it by substituting the upstream response, so what ran was the recipe that ships rather than a copy of its logic: a CRD with no controller-gen line fails with `stamped 0 keep annotations, expected 1`, a CRD carrying that line twice fails with 2, a failure on the second of the three leaves all three committed files untouched, and the real upstream still vendors byte-identical output, with `make update` leaving `git status` showing nothing but this Makefile. On `main` today this is the only thing guarding those three CRDs. A helm-unittest suite that fails when one of them renders without the annotation is in #3590, which is open and not merged. Once that lands the two catch the same loss at different moments: the suite on a test run, this step in front of whoever ran the regeneration. The same stamping shape exists in three more packages, but only on the branch of #3586, which adds it to `gateway-api-crds`, `vsnap-crd` and `vertical-pod-autoscaler-crds`. There is nothing to guard there on `main` yet, so they are deliberately out of this change and need the same step once #3586 merges. ### Screenshots Not a UI change. ### Downstream repositories - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: I walked the trigger map in `docs/agents/contributing.md` against the file list of this diff, which is one package Makefile and nothing else. `hack/package.mk` is untouched, so the `ccp` and `external-apps-example` triggers that key on it do not fire, and the website's "developer tooling (the package Makefiles)" line is the only near miss: `content/en/docs/next/development.md` describes `make update` in general terms and does not go stale from a package validating its own output. ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved CRD update validation to ensure generated definitions include the required preservation annotation. * Prevented incomplete or invalid updates from replacing existing CRD templates. * Added automatic cleanup of temporary update files. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What this PR does
Four tenant addon HelmReleases render their CRDs from a chart's
templates/directory and remediate a failed install by uninstalling the release. An uninstall deletes what the chart rendered, and deleting a CRD makes the API server garbage-collect every custom resource of that kind, so a failed reinstall takes every Gateway, VerticalPodAutoscaler, ServiceMonitor or VolumeSnapshot in the tenant cluster with it. This annotates the CRDs of the four packages behind them,gateway-api-crds,vertical-pod-autoscaler-crds,prometheus-operator-crdsandvsnap-crd(that last one is declared byhelmreleases/volumesnapshot-crd.yaml), withhelm.sh/resource-policy: keep, which is whatcert-manager-crdsandetcd-operator-crdsalready carry in this tree.Three of the four regenerate
templates/from upstream onmake update: two delete the directory outright and the third overwrites its file. So the annotation is written into the vendored files and reapplied by a step in eachupdate:target. Without that second half the next regeneration drops it silently, with nothing reporting the loss. I ranmake updatefor real to check the pair holds:gateway-api-crdsandvsnap-crdcome back byte-identical to what is committed here, andvertical-pod-autoscaler-crdsreproduces the annotation on both of its CRDs. The rest of the VPA diff is upstream drift, because that package'supdate:tracks a branch rather than a pinned ref; the bump itself is not in this PR. The step is idempotent, so running it twice does not stack a second annotation into the same mapping.prometheus-operator-crdsships its CRDs in a vendored subchart thatmake updatere-pulls, so nothing undercharts/is edited. The upstream template already renders.Values.annotationsonto every CRD it emits, and the annotation is set through the package'svalues.yaml.The Gateway API kustomization also emits a
ValidatingAdmissionPolicyand its binding. Those hold no user data, so the stamping step selects onkind: CustomResourceDefinitionand leaves them alone.Each package gains a helm-unittest suite and the
test:target thathack/helm-unit-tests.shrequires before it will run a suite at all. Every suite asserts the annotation across all CRDs its chart renders, not only the ones listed by name, so a CRD arriving in a later upstream bump is covered without editing the test.The annotation has a cost worth stating plainly: uninstalling one of these releases on purpose now leaves the CRDs behind, to be removed by hand. That is the same trade-off
cert-manager-crdsalready carries.This covers the first of the two directions in #3555. The second one, setting
install.strategy.name: RetryOnFailureon these releases, is a separate decision with a different blast radius and is not part of this PR.Screenshots
Not a UI change.
Downstream repositories
I walked the trigger map in
docs/agents/contributing.mdagainst the file list of this diff. It touches four packages underpackages/system/and nothing else: nohack/package.mk, no appvalues.schema.json, no platform values, noApplicationDefinition, no release asset names. The one line that looked close is the website's "developer tooling (the package Makefiles)" trigger, so I readcontent/en/docs/next/development.mdin the website repo: it documentsmake updateandmake testin general terms, and neither goes stale from this change.Release note
Summary by CodeRabbit