fix(kubevirt-cdi-operator): pin the vendored CDI release - #4383
Conversation
This package decides which CDI the platform runs, but its update recipe took the version from GitHub's releases/latest redirect. Any refresh, including one done for an unrelated reason, could move the deployed CDI and its seven image tags without anyone choosing a release, and the diff carried no version line to review. That version is becoming load-bearing for behaviour, not only for images: logic that enumerates CDI's DataVolume phases is correct against one release. The release is now named in the Makefile, the asset is checked against a digest before anything is written, and a refresh that fails partway leaves the vendored manifest untouched. The digest guards against an asset replaced under its tag; it is not an authenticity check. A test target ties the pin to the tree: helm-unittest reads only the vendored file, so a pin moved without re-running update would otherwise leave every assertion green against the old release. The suite also holds the two local patches and the namespace rewrite that every refresh has to reapply. The pinned release is the one already vendored, and update reproduces templates/ byte for byte. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: cozystack/cozystack/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review. 📝 WalkthroughWalkthroughThe CDI operator package now pins release v1.66.1 and verifies the downloaded manifest before updating the vendored copy. A new test target and Helm test suite check the pinned image and selected manifest content. ChangesCDI manifest update
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Suggested reviewers: Merge Risk: ⚪ Minimal · up to The pinned update workflow and vendored-manifest checks have no identified merge-blocking risk; failed transformations leave the existing manifest in place. 🚥 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 |
## What this PR does `make update` in `packages/system/kubevirt-cdi` began with `rm -rf templates` and then refetched one file, `cdi-cr.yaml`. Nothing in that directory is purely vendored. Two of the three manifests, the upload-proxy Ingress and TLSRoute from the Gateway API work, are ours and no fetch recreates them, so the next CDI bump would have dropped upload-proxy exposure from the chart and still exited 0. The third is `cdi-cr.yaml` itself, which despite its name carries Helm templating, the `ExpandDisks` gate, a toleration and the two RBAC documents that preauthorize cloning from `cozy-public`; the fetch overwrites it, and removing the wipe does not change that. That recipe now opens with `mkdir -p templates` and deletes nothing, which is where `packages/system/kubevirt-operator` landed at the last KubeVirt bump. The wipe bought nothing there anyway: `wget -O` truncates the file it writes. `kubevirt-cdi-operator` has the same recipe shape, and the same edit there would be a regression rather than a fix. It applies two patches, and since #3920 moved them to `-p4` they resolve, so a hunk upstream has moved out from under leaves `templates/cdi-operator.yaml.rej`; its `awk -i inplace` step leaves `templates/cdi-operator.yaml.gawk.XXXXXX` if gawk dies abnormally. Helm renders every file under `templates/`, and I reproduced both: the reject fails the package with a YAML parse error, and the gawk temp renders a duplicate object at exit 0. The wipe is what takes both, so removing it for symmetry would trade a latent loss for a live one, and the quieter of the two is the one that survives. Deleting the wipe there wants the recipe restructured to transform outside `templates/` and move the result in once, which is what `kubevirt-operator` does today and what its own comment gives this exact reason for. That is a larger change than this one and belongs on its own; #4383 makes it. Meanwhile the scan below covers the package: I added a first-party file to its `templates/` and the guard reported it, so the day that package grows one, this fails rather than the file being lost. `hack/package-update-templates.bats` guards this across the tree rather than in these two packages. It scans every package Makefile and fails when a recipe that clears its own `templates/` leaves behind a tracked file it never names. Put the removal back into `kubevirt-cdi` and it goes red naming both upload-proxy files. A file counts as named only outside the Makefile's own `NAME` and `NAMESPACE`, however the assignment is written. The match is on the variable name rather than on a list of spellings: the tree already carries three of them, `export NAME=`, the bare `NAME=`, and `NAME := ` in `packages/core/testing`, and the hole is identical in each. Without that, a package called `foo-bar` satisfies the check for its own `templates/bar.yaml` through its identity alone. Nothing the scan reaches is exempted that way as the tree stands, so the exclusion is there for the first package that would be: rename the asset in `kubevirt-cdi-operator`'s recipe and put the removal back, and the guard reports the orphaned template only with the exclusion in place. The two variables are matched by name rather than by a `[A-Z_]+=` shape, because a shape that broad is one unspaced assignment away from swallowing a variable that does name templates, which is how `etcd-operator-crds` names all five of its CRDs. A control test runs the same helpers over a fixture broken the same way, over one whose name swallows a template stem, then over that fixture rewritten to spell the removal with a trailing slash, to take one file instead of the directory, and to drop the removal entirely. Both cases pass under `hack/cozytest.sh hack/package-update-templates.bats`. Two limits, both written into the file's header. The check fires on the consequence rather than the shape, so `rm -rf templates` in a package that does refetch everything stays green, which is what keeps `kubevirt-cdi-operator` green for as long as its recipe wipes. And it only looks at a recipe clearing its own directory: `cert-manager` clears `../cert-manager-crds/templates/*` across the package boundary, which needs the removal's target resolved rather than the wiping package's own, so that directory is unguarded. The other packages that clear their own `templates/` refetch all of it and are untouched here. Two more limits the header names. The scan reads the text of each package's own `update:` recipe, so a removal made elsewhere is out of range: in a prerequisite target, behind an include, or behind a recursive make. `capi-operator`, `clustersecret-operator`, `openbao` and `reloader` write `update: clean <name>-update` and take both recipes from `hack/package.mk`, which removes nothing under `templates/` today. And the glob stops at `packages/*/*/Makefile`. The package Makefiles off that depth are the group aggregators such as `packages/system/Makefile` and the vendored kamaji subcharts, and none of them declares `update`. One behaviour of the matcher worth naming, and deliberately not changed here. `wipe_command` evaluates `/^update:/` ahead of its terminator, so a second `update:` in the same Makefile re-arms the scan instead of ending it, and what that means depends on what sits between the two: adjacent or separated by a blank line, both recipes are read; separated by another target, only the first is. The direction that matters is a false positive rather than a miss. A Makefile whose first `update:` clears `templates/` and whose second does not would be reported by this scan, while make runs only the second, so the package fails over a recipe that never executes. No package in the tree defines `update:` twice today. Three more notes, all non-blocking and none acted on. The header says what the scan leaves uncovered is a recipe clearing someone else's `templates/`; that is the uncovered set for *deletion*, and `kubevirt-cdi` is green under the scan while still losing `cdi-cr.yaml`'s content to the in-place `wget -O` on every run, which the Makefile comment records but the header does not. The identity strip has a false-positive direction the header does not argue: a wiping package writing `templates/$(NAME).yaml` would be reported even though the recipe writes it, which no package does today. And the fixture stages `.gitkeep` with `git add --all`, so a developer whose global excludes list that name would get a red for environment rather than defect; `--force` would remove the dependency. The stem match is generous, and the helper's comment now names the two live shapes. A sibling name: `templates/crds.yaml` next to the existing `templates/crds-experimental.yaml` on `gateway-api-crds`. A mention from an unrelated context: `templates/sidecar.yaml` on `objectstorage-controller`, whose Makefile says `sidecar` across its image targets while its recipe clears `templates/` and writes only `templates/controller.yaml`. I fed both to the helper and both come back exempted; a stem the Makefile never mentions is still reported. The duplicate `test:` target in this Makefile is #4157 and stays there. It sits outside the hunks this diff touches, so removing it here would be reaching past what this change is about. One more note, not a defect. Where the comment points at `kubevirt-operator` as the shape that closes the transient-file class, `packages/system/kubevirt/Makefile` is the closer analogue for the `cdi-cr.yaml` half specifically: its `update` downloads nothing and re-applies the parameterization onto a hand-maintained CR with a per-marker self-check. And the failure message offers a third option, staging into a tmpdir the way `etcd-operator-crds` does, in a parenthetical that reads as a gloss on the option before it. What this PR does not fix, and what does. `make update` here still overwrites `templates/cdi-cr.yaml`, which the Makefile comment says plainly. A wholesale overwrite is loud: upstream's object carries neither `podResourceRequirements` nor `cloneStrategyOverride`, and `tests/importer-resources_test.yaml` and `tests/clone_strategy_test.yaml` pin both, so `helm unittest` goes red. The silent case is a partial hand reconciliation that keeps those two and drops something else, because nothing here pins the `ExpandDisks` gate, the `CriticalAddonsOnly` toleration, the `uploadProxyURL` override or the two RBAC documents that preauthorize tenant cloning from `cozy-public`. #4107, stacked on this branch, stops `update` writing the file at all and adds `tests/local_templates_test.yaml`, which pins those. One limit the header does not name, left alone here. The comment strip is anchored to the start of a line, so a trailing comment on a recipe line survives into the searched text and counts as naming the file it mentions. I confirmed it by feeding `wget ... -O templates/cdi-cr.yaml # local-extra.yaml is written by hand` through the filter. Nothing in the tree is written that way, and the header only claims comment lines are dropped, which is what it does. `sed 's/#.*//'` in place of the `grep -v` closes it if it ever matters. Closes #4028. ### Screenshots No UI change. ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff. Two entries come close: the website's "developer tooling (the package `Makefile`s)" line, and ccp's "move or rename anything under `hack/`". Neither fires. `make update` still behaves as the website's development page describes, nothing under `hack/` moved or was renamed, and the two files ccp's skills key on (`hack/package.mk`, `hack/common-envs.mk`) are untouched. - [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: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note fix(cdi): `make update` in kubevirt-cdi no longer deletes its first-party templates ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Updated Kubernetes CDI package updates to preserve existing template files while refreshing downloaded manifests. - Prevented update operations from clearing first-party and vendored template content. - **Tests** - Added safeguards to detect package update recipes that could remove tracked templates without restoring them. - Added Helm unit test support for the Kubernetes CDI package. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
LGTM with non-blocking notes
Two gaps in what the new suite actually pins. Neither blocks the merge.
Findings
- [MINOR]
packages/system/kubevirt-cdi-operator/tests/vendored_manifest_test.yaml:37, the two asserts that coverpatches/pin presence, not content
- [NIT]
packages/system/kubevirt-cdi-operator/Makefile:32, the freshness grep splicesRELEASEinto a BRE unescaped
Caveats
- I ran the mechanism claims in the new comments instead of taking them on trust, and they hold. Without
.PHONY,make testandmake updateboth reportis up to dateand skip the recipe when a path of that name sits beside the Makefile (GNU Make 3.81). Dropany: truefrom the negatedcontainsDocumentand it stays green with aNamespaceinjected at index 1.documentSelectorfails withmultiple indexes foundon a secondDeployment.patch <file> < diffapplies both patches without-p4, byte for byte. make updatereproducedtemplates/cdi-operator.yamlbyte for byte in a throwaway container with GNU sed and gawk, and the published v1.66.1 asset hashes to the pinnedMANIFEST_SHA256. Forcing a digest mismatch lefttemplates/untouched and removed the partial download.- Rendered output is identical to the merge base, so neither an upgrade nor a fresh install sees anything new. No
values.yamlin this chart, so there's no toggle matrix to walk. Every check on8dfbaa1dpasses. - CI does reach
make test:hack/helm-unit-tests.shruns it for everypackages/system/*whosemake -n testresolves, and that script already fails a package whosetesttarget comes backis up to date.
Recommended follow-ups
- Nothing advances
RELEASEnow that thereleases/latestredirect is gone..github/renovate.jsonenablescustom.regexbut has no manager matching a package Makefile, so CDI moves when someone remembers to move it.kubevirt-operatorhas the same gap, so one manager covers both. - Nothing checks the committed
templates/against whatupdatewould produce. The digest pins the input, not the output, so a hand edit anywhere in those 358 KB passes every check. Output is deterministic now, so a scheduledmake update && git diff --exit-codewould close it. packages/system/kubevirt-cdi/Makefile:15still resolves the same upstream project throughreleases/latest, and it declarestest:twice. PR #4107 is open for that half.
| path: metadata.namespace | ||
| value: cozy-kubevirt-cdi | ||
| # Carried by patches/, which `update` re-applies to every download. | ||
| - exists: |
There was a problem hiding this comment.
[MINOR] the two asserts that cover patches/ pin presence, not content
patches/startupProbe.diff adds a probe on /healthz:8444, patches/tolerations.diff adds three toleration entries. The suite asserts that the startupProbe key exists and that the tolerations list contains CriticalAddonsOnly. Both still pass when the patched content is wrong: repointing the probe at /wrong:9999 and deleting the two node-role.kubernetes.io/* entries leaves the suite green, and the Makefile grep looks only at the image tag, so make test exits 0 on a manifest that would schedule nowhere and never pass startup. equal against the full probe block and the full three-entry list closes both.
$ cd packages/system/kubevirt-cdi-operator
$ perl -0pi -e 's{(startupProbe:\n httpGet:\n path: )/healthz(\n port: )8444}{${1}/wrong${2}9999}' templates/cdi-operator.yaml
$ perl -0pi -e 's{ - effect: NoSchedule\n key: node-role\.kubernetes\.io/(?:control-plane|master)\n operator: Exists\n}{}g' templates/cdi-operator.yaml
$ make test; echo "make exit=$?"
helm unittest .
### Chart [ cozy-kubevirt-cdi-operator ] .
PASS cozy-kubevirt-cdi-operator vendored manifest tests/vendored_manifest_test.yaml
Charts: 1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshot: 0 passed, 0 total
Time: 91.36175ms
make exit=0
$ git checkout -- templates/cdi-operator.yaml
| @# helm-unittest reads the vendored file and cannot see RELEASE, so without | ||
| @# this a pin moved without re-running `update` leaves every assertion | ||
| @# green against the old release. | ||
| @if ! grep -q 'image: quay.io/kubevirt/cdi-operator:$(RELEASE)$$' templates/cdi-operator.yaml; then \ |
There was a problem hiding this comment.
[NIT] the freshness grep splices RELEASE into a BRE unescaped
The dots in v1.66.1 are wildcards in that pattern, so it also matches cdi-operator:v1x66x1. The case the guard exists for, RELEASE moved without a re-run of update, leaves a wholly different tag in the file, so nothing reachable trips it today. Escaping the dots keeps the $ anchor and closes it.
What this PR does
This pins the CDI release the platform deploys, so it moves only when someone edits a version line. Until now
make updateinpackages/system/kubevirt-cdi-operatortook the release from GitHub'sreleases/latestredirect. Any refresh could move CDI and its image tags, and the diff had no version line to review. The fix follows the shapepackages/system/kubevirt-operatoralready uses.RELEASE = v1.66.1names the release. It is the one already vendored, sotemplates/does not change, andmake updatereproduces it byte for byte.MANIFEST_SHA256pins the downloaded asset and matches the digest GitHub publishes for it. On a mismatch the recipe writes nothing.templates/and moved in at the end. A refresh that fails halfway leaves the vendored file as it was.testtarget runs a helm-unittest suite over the vendored manifest, then greps it for the operator image at$(RELEASE). The grep is there because helm-unittest cannot see a Makefile variable. Moving the pin without re-runningupdatefails here.One thing is left for later. The suite asserts three of the eight image references directly, and the digest keeps the rest in step on
update, but a hand edit to one of the other image env entries would pass.Closes #4190
Screenshots
Downstream repositories
Nothing on the trigger map applies. The diff touches one package's own Makefile,
.gitignoreand tests, and no shared tooling, values, CRD, namespace or release asset.Release note
Summary by CodeRabbit