Skip to content

fix(kubevirt-cdi-operator): pin the vendored CDI release - #4383

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/cdi-operator-pin-release
Sep 23, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/cdi-operator-pin-release

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

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 update in packages/system/kubevirt-cdi-operator took the release from GitHub's releases/latest redirect. Any refresh could move CDI and its image tags, and the diff had no version line to review. The fix follows the shape packages/system/kubevirt-operator already uses.

  • RELEASE = v1.66.1 names the release. It is the one already vendored, so templates/ does not change, and make update reproduces it byte for byte.
  • MANIFEST_SHA256 pins the downloaded asset and matches the digest GitHub publishes for it. On a mismatch the recipe writes nothing.
  • The download is transformed and patched outside templates/ and moved in at the end. A refresh that fails halfway leaves the vendored file as it was.
  • A new test target 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-running update fails 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, .gitignore and tests, and no shared tooling, values, CRD, namespace or release asset.

Release note

fix(kubevirt-cdi-operator): pin the vendored CDI release to v1.66.1 and verify the downloaded manifest against a digest, so refreshing the package no longer moves the deployed CDI. The deployed version stays the same.

Summary by CodeRabbit

  • Updates
    • The KubeVirt CDI Operator is now pinned to version 1.66.1, providing a consistent operator version across deployments.
  • Reliability
    • Operator manifest updates are checked before they’re applied, helping prevent incomplete or mismatched manifests from replacing the current version.
    • Deployment configuration is validated to ensure it matches the pinned release.

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]>
@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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 configuration

Configuration used: Repository: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: d8cb2f70-8fab-441b-ab67-97fdca8db9ac

📥 Commits

Reviewing files that changed from the base of the PR and between a759190 and 8dfbaa1.

📒 Files selected for processing (3)
  • packages/system/kubevirt-cdi-operator/.gitignore
  • packages/system/kubevirt-cdi-operator/Makefile
  • packages/system/kubevirt-cdi-operator/tests/vendored_manifest_test.yaml

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


📝 Walkthrough

Walkthrough

The 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.

Changes

CDI manifest update

Layer / File(s) Summary
Pinned and verified manifest update
packages/system/kubevirt-cdi-operator/Makefile, packages/system/kubevirt-cdi-operator/.gitignore
The update target pins release v1.66.1 and its manifest digest. It downloads to a temporary file, verifies the digest, applies transformations and patches, then replaces the vendored manifest. The temporary file is ignored.
Vendored manifest validation
packages/system/kubevirt-cdi-operator/Makefile, packages/system/kubevirt-cdi-operator/tests/vendored_manifest_test.yaml
The test target runs Helm unit tests and checks for the pinned operator image. The test suite checks the Deployment image, environment, namespace, startup probe, toleration, and absence of Namespace documents.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: kvaps

Merge Risk: ⚪ Minimal · up to 8dfba

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)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: pinning the vendored CDI release.
Linked Issues check ✅ Passed [#4190] requires a chosen CDI release instead of resolving releases/latest during make update, with a digest check for the downloaded manifest. The Makefile sets RELEASE = v1.66.1, pins `MANIFES…
Out of Scope Changes check ✅ Passed All changed files support [#4190]. The Makefile changes pin and verify the release; the manifest tests check the vendored release and transformations; .gitignore excludes the temporary download used…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

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

@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug labels Sep 22, 2026
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review September 22, 2026 21:30
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Sep 23, 2026
## 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 IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 cover patches/ pin presence, not content
  • [NIT] packages/system/kubevirt-cdi-operator/Makefile:32, the freshness grep splices RELEASE into 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 test and make update both report is up to date and skip the recipe when a path of that name sits beside the Makefile (GNU Make 3.81). Drop any: true from the negated containsDocument and it stays green with a Namespace injected at index 1. documentSelector fails with multiple indexes found on a second Deployment. patch <file> < diff applies both patches without -p4, byte for byte.
  • make update reproduced templates/cdi-operator.yaml byte for byte in a throwaway container with GNU sed and gawk, and the published v1.66.1 asset hashes to the pinned MANIFEST_SHA256. Forcing a digest mismatch left templates/ 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.yaml in this chart, so there's no toggle matrix to walk. Every check on 8dfbaa1d passes.
  • CI does reach make test: hack/helm-unit-tests.sh runs it for every packages/system/* whose make -n test resolves, and that script already fails a package whose test target comes back is up to date.

Recommended follow-ups

  • Nothing advances RELEASE now that the releases/latest redirect is gone. .github/renovate.json enables custom.regex but has no manager matching a package Makefile, so CDI moves when someone remembers to move it. kubevirt-operator has the same gap, so one manager covers both.
  • Nothing checks the committed templates/ against what update would 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 scheduled make update && git diff --exit-code would close it.
  • packages/system/kubevirt-cdi/Makefile:15 still resolves the same upstream project through releases/latest, and it declares test: 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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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 \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 9e0e123 into main Sep 23, 2026
20 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/cdi-operator-pin-release branch September 23, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kubevirt-cdi-operator resolves its CDI version at update time, so the deployed release moves without anyone choosing it

2 participants