Skip to content

fix(api): leave failed golden images out of the image options - #4568

Merged
IvanHunters merged 2 commits into
mainfrom
fix/image-options-skip-failed-goldens
Oct 5, 2026
Merged

IvanHunters merged 2 commits into
mainfrom
fix/image-options-skip-failed-goldens

Conversation

@lexfrei

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

Copy link
Copy Markdown
Contributor

What this PR does

The VM image picker no longer offers golden images whose import failed. A golden keeps its PVC in cozy-public after the import fails, so a picker built on the PVC list offered an image whose clone never completes.

The image option source in cozystack-api now reads the vm-default-images-* DataVolumes next to the PVCs and drops names whose DataVolume is in phase Failed. I filter rather than mark them, because such an option can never be used and OptionItem has no disabled state. A golden that is still importing stays in the list, since CDI holds a clone until the source is populated, and so does a PVC with no DataVolume. When the DataVolumes cannot be read, the PVCs go out unfiltered. The Option list drops a source whose provider errors, and an empty picker would be worse than the old behaviour. cozystack-api gets a read grant on cdi.kubevirt.io DataVolumes for this.

The console did not use that source at all. SourceField listed the PVCs itself, as the tenant, so the fix above would not reach the picker users see. It now reads the same Option list the other dynamic dropdowns use. With that, nothing in the console lists cozy-public PVCs, so I removed the read grant on them from the cozy:tenant:dashboard Role. That is a visible change: a tenant loses kubectl get pvc --namespace cozy-public, which was only ever granted for this picker in #2843.

One limitation: in CDI v1.66.1, which cozystack ships, a golden import reaches Failed only on a checksum mismatch, and a broken URL keeps it in ImportInProgress. So this filter covers less than the issue assumed. I filed #4564 for that.

Closes #4553

Screenshots

I have no screenshot. The picker looks the same; only the list behind it changes. apps/console/src/components/SourceField.test.tsx covers it: the picker shows the images the option source serves and not the raw PVC names.

Downstream repositories

Release note

fix(api): the VM image picker no longer offers golden images whose import failed. The console now reads images from the image option source, and tenants no longer get read access to PersistentVolumeClaims in cozy-public.

Summary by CodeRabbit

  • New Features
    • The image picker now displays available images from the tenant’s image options.
  • Bug Fixes
    • Images whose imports have failed are excluded from the picker. Images remain available when import status is still in progress, has succeeded, or cannot be retrieved.

The image option source lists every vm-default-images-* PVC in
cozy-public. CDI creates that PVC as soon as an import starts, so a
golden whose DataVolume ended in phase Failed is still offered, and a
disk cloned from it never populates: the user picks an image the
platform just suggested and ends up with a disk that never becomes
usable.

Read the DataVolumes next to the PVCs and drop the names whose import
Failed. Filtering beats marking: the option can never be used, and
OptionItem has no disabled state to show it in. A golden still
importing stays, since CDI holds a clone until the source is
populated, and so does a PVC with no DataVolume. When the DataVolumes
cannot be read, without CDI or without the grant, the PVCs go out
unfiltered: the Option list drops a source whose provider errors, and
an empty picker would be a regression rather than a safeguard.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
The vm-disk source picker listed vm-default-images-* PVCs in
cozy-public directly, as the tenant, so it kept offering goldens whose
import failed even once the image option source leaves them out. The
Option list is what the other dynamic dropdowns already read, and it
shares their request.

With the console no longer listing those PVCs, the tenant dashboard
role's read on cozy-public PVCs has no user left, so it goes too.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@github-actions github-actions Bot added area/api Issues or PRs related to the cozystack-api aggregated API server size/L This PR changes 100-499 lines, ignoring generated files kind/bug Categorizes issue or PR as related to a bug labels Sep 29, 2026
@coderabbitai

coderabbitai Bot commented Sep 29, 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: a89c278b-cb9d-4fc4-94e9-1198c9f9e88d

📥 Commits

Reviewing files that changed from the base of the PR and between b314514 and 0ae50c6.

📒 Files selected for processing (9)
  • packages/system/cozystack-api/templates/rbac.yaml
  • packages/system/cozystack-api/tests/rbac_test.yaml
  • packages/system/cozystack-basics/templates/dashboard-role.yaml
  • packages/system/cozystack-basics/tests/dashboard-role_test.yaml
  • packages/system/dashboard/images/console/apps/console/src/components/DynamicOptionsWidget.tsx
  • packages/system/dashboard/images/console/apps/console/src/components/SourceField.test.tsx
  • packages/system/dashboard/images/console/apps/console/src/components/SourceField.tsx
  • pkg/registry/core/option/providers.go
  • pkg/registry/core/option/providers_test.go
💤 Files with no reviewable changes (1)
  • packages/system/cozystack-basics/templates/dashboard-role.yaml

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


📝 Walkthrough

Walkthrough

The image provider now excludes golden-image PVCs when their matching DataVolume has phase Failed. The dashboard image picker reads image choices from the tenant’s options resource. RBAC changes grant the API read access to DataVolumes and remove the dashboard role’s PVC read access in cozy-public.

Changes

Image picker options

Layer / File(s) Summary
Image-option access rules
packages/system/cozystack-api/templates/rbac.yaml, packages/system/cozystack-api/tests/rbac_test.yaml, packages/system/cozystack-basics/templates/dashboard-role.yaml, packages/system/cozystack-basics/tests/dashboard-role_test.yaml
The API role gains read access to CDI DataVolumes. The tenant dashboard role loses PVC read access in cozy-public. Tests check the new API rule, rule count, and dashboard permissions.
Filter failed image imports
pkg/registry/core/option/providers.go, pkg/registry/core/option/providers_test.go
The image provider excludes a golden-image PVC when its matching DataVolume phase is Failed. Tests cover other phases and cases where listing DataVolumes fails.
Read image options from the tenant
packages/system/dashboard/images/console/apps/console/src/components/DynamicOptionsWidget.tsx, packages/system/dashboard/images/console/apps/console/src/components/SourceField.tsx, packages/system/dashboard/images/console/apps/console/src/components/SourceField.test.tsx
SourceField queries the tenant’s options resource and uses the image option’s items. The test checks that an image option appears and a PVC does not.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 0ae50

The image picker now shows images from the tenant options source and hides images whose import failed. The change is small and tested, and no merge-blocking risk was identified.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 0ae50

The change reduces direct dashboard access to shared storage, but grants the API broader read access than its image picker needs. The picker also returns to its previous, unfiltered choices if import status cannot be read.

Retained concerns

  • Medium · security · inferred: The new CDI read grant covers DataVolumes in every namespace, while the image provider uses it only for cozy-public. Compromise of the API identity would permit additional cross-tenant DataVolume reads. The grant is read-only, and this does not demonstrate a tenant-facing authorization bypass.
Security review details

Security Blast Radius

  • inferred — The new grant permits the API ServiceAccount to read DataVolumes across namespaces, beyond the public catalog namespace used by this provider. The changed picker exposes normalized catalog values, not those cross-namespace objects.

Security Findings and Attack Paths

  • inferred — An attacker with the API ServiceAccount's authority could use the new grant to read other namespaces' DataVolume objects. No path from an ordinary tenant's Option request to those objects was established, and no verified Security finding was supplied.

Trust Boundaries and Controls

  • observed — The dashboard requests Options in its tenant namespace, and Option REST uses the request namespace to label results. The provider limits image-source reads to cozy-public. Effective authorization of tenant Option requests remains outside the verified source path.

Resilience and Maintainability Implications

  • observed — Failed-import exclusion is advisory: a forbidden or unavailable DataVolume list restores unfiltered choices. This matches the previous picker exposure under that failure condition, rather than introducing a new security control that fails closed.

Hardening Proposals

  • proposed — Consider limiting the API's CDI read authority to cozy-public if no other API path needs cluster-wide DataVolume access, and make loss of failure filtering observable during rollout.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: excluding failed golden images from image options.
Linked Issues check ✅ Passed [#4553] imageProvider still lists cozy-public PVCs and strips vm-default-images-. It lists CDI DataVolumes in the same namespace and excludes only matching images with status.phase == "Failed"…
Out of Scope Changes check ✅ Passed The changed code supports [#4553]. The console picker change consumes the filtered option source. The API RBAC change permits the DataVolume lookup. The dashboard Role change removes the obsolete dire…
Full details: Docstring Coverage

Explanation

Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 5 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 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.

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

LGTM

A clean, well-scoped fix. There are three ways a filter like this usually goes wrong, and I checked each one against the code.

The "failed" decision reads status.phase on the DataVolume and compares it to Failed (pkg/registry/core/option/providers.go:357). That is CDI's own phase field, not a status.conditions[] near-twin and not a frozen lastTransitionTime, so it follows the live import. Only Failed drops. An image still importing (ImportInProgress), one that succeeded, one still pending, or a PVC with no DataVolume at all, all stay in the list, and the tests name the ImportInProgress and no-DataVolume cases. The filter keys on the DataVolume name and matches it against the PVC name, which is sound because a golden's DataVolume and its target PVC in cozy-public carry the same name vm-default-images-<name> (packages/system/vm-default-images/templates/dv.yaml, usePopulator: "true"). Nothing drops a healthy image off by one.

I took this to the code rather than the PR description. Reverting the || failed[name] term reddens TestImageProviderSkipsGoldensWhoseImportFailed, so the test holds the behaviour instead of passing beside it, and go build / go test on the package are green. The RBAC count bound (20 to 21) and the dashboard-role flip from contains to notContains ... any: true track the diff, and the console now reads the same image Option the other dropdowns use, so the server-side filter actually reaches the picker.

Two notes inline, neither blocking.

verbs: ["get", "watch", "list"]
# The image picker drops golden images whose import Failed.
- apiGroups: ["cdi.kubevirt.io"]
resources: ["datavolumes"]

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] cdi.kubevirt.io/datavolumes read grant is cluster-wide while the provider only reads cozy-public

This grant is cluster-wide, but imageProvider only ever lists DataVolumes in cozy-public (pkg/registry/core/option/providers.go:351). It is read-only and never reaches a tenant, and it matches the other cluster-scoped read grants on this ClusterRole (nodes, kubevirts, instancetypes), so I am not blocking on it. If no other API path needs the wider read, a namespaced Role on cozy-public would be tighter. Read-only cross-namespace exposure only, no tenant-facing path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving this cluster-wide. The same ClusterRole already lets cozystack-api get and list every Secret in the cluster, so DataVolume read adds nothing it can not already see. A Role in cozy-public can not ship from this chart either: cozystack-basics creates that namespace and dependsOn cozystack-engine, so on a fresh install the Role would hit a missing namespace.

// imageProvider lists the default image PVCs in cozy-public and strips the
// vm-default-images- prefix to get the catalog name used by the vm-disk chart.
//
// A golden whose DataVolume is in phase Failed is left out: CDI never

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 Failed filter covers less than #4553 assumed (documented, tracked in #4564)

Worth keeping visible next to the code: in CDI v1.66.1 a golden reaches Failed only on a checksum mismatch, while a broken source URL stays in ImportInProgress and is therefore never filtered out of the picker. This is called out in the PR body and tracked in #4564, so it is an acknowledged scope limit rather than a defect in this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it stays open in #4564 until there is a better signal than Failed.

@IvanHunters
IvanHunters merged commit 6a98acb into main Oct 5, 2026
58 of 59 checks passed
@IvanHunters
IvanHunters deleted the fix/image-options-skip-failed-goldens branch October 5, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api Issues or PRs related to the cozystack-api aggregated API server 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.

image option source offers golden images whose import failed

2 participants