Skip to content

fix(capi): use exact API-group matching in CAPI renderer guards - #1618

Closed
changyonggang wants to merge 1 commit into
skyhook-io:mainfrom
changyonggang:fix/capi-renderer-exact-group-match
Closed

changyonggang wants to merge 1 commit into
skyhook-io:mainfrom
changyonggang:fix/capi-renderer-exact-group-match

Conversation

@changyonggang

@changyonggang changyonggang commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

Three CAPI renderer guards in packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx matched the API group with apiVersion?.includes(\"cluster.x-k8s.io\"). That substring test also matches a foreign group whose name ends in the same suffix (e.g. extension.cluster.x-k8s.io), so a generic CRD would render through a CAPI renderer, get a fabricated CAPI status, or receive the topology-controlled ClusterClass warning banner.

Replaced the four remaining substring checks with the existing isApiGroup(..., 'cluster.x-k8s.io') helper, following the exact-group pattern already used for CAPI clusters on line 861 and the CNPG/Velero collision handling.

machines and machinesets are in KNOWN_KINDS, which suppresses the generic renderer. With the strict guard, a foreign CRD sharing those plurals would match neither the CAPI render line nor the generic fall-through and would render a blank drawer — the exact trap the Crossplane block documents. Added a capiCollisionFallthrough that routes such kinds to GenericRenderer, mirroring the existing groupGatedFallthrough for clusters, backups, etc.

Read docs/INTEGRATION_GUIDE.md before reviewing renderer-dispatch changes.

Type of change

  • Bug fix (non-breaking change that fixes an issue)

How has this been tested?

  • Added/updated unit tests

Regression coverage in ResourceRendererDispatch.test.tsx uses a foreign extension.cluster.x-k8s.io/v1 CRD and asserts, at all three boundaries the issue calls out, that a substring guard would have failed:

  • Renderer dispatch for clusters, machines, machinesets — foreign CRD renders through GenericRenderer, no CAPI-specific text
  • getResourceStatus for clusters and machines — foreign CRD gets the generic phase-echo, not the CAPI badge
  • Topology-controlled warning banner — the topology.cluster.x-k8s.io/owned label on a foreign group does not trigger the ClusterClass warning
  • Positive check: a real cluster.x-k8s.io/v1beta1 resource still shows the warning

Test results:

  • `npx vitest run` in `packages/k8s-ui`: 161 files / 3389 tests pass (1 pre-existing skip)
  • `make tsc`: green

Checklist

  • My code follows the project's coding standards
  • I have performed a self-review of my code
  • I have added comments where necessary
  • My changes generate no new warnings

Related issues

Fixes #1582


Note

Low Risk
UI-only dispatch and status logic for resource detail views; tightens matching so fewer foreign CRDs are mislabeled, with no auth or data-path changes.

Overview
Fixes mis-routing of CRDs whose API group merely contains cluster.x-k8s.io (e.g. extension.cluster.x-k8s.io) through Cluster API UI paths.

ResourceRendererDispatch now gates the topology-controlled ClusterClass banner, machines / machinesets CAPI renderers, and CAPI machine status on isApiGroup(..., 'cluster.x-k8s.io') instead of substring includes. Foreign machines / machinesets that share those plurals but are not CAPI are sent to GenericRenderer via new capiCollisionFallthrough, avoiding blank drawers after the stricter guards.

Regression tests cover renderer dispatch, status badges, and the topology banner for extension.cluster.x-k8s.io, plus a positive check for real CAPI resources.

Reviewed by Cursor Bugbot for commit 1db0a91. Bugbot is set up for automated code reviews on this repo. Configure here.

Three CAPI guards in ResourceRendererDispatch matched the API group with
`apiVersion?.includes("cluster.x-k8s.io")`. That also matches a foreign
group ending in the same suffix (e.g. `extension.cluster.x-k8s.io`), so
a generic CRD would render through a CAPI renderer, get a fabricated
CAPI status, or receive the "Topology-controlled" ClusterClass warning.

Replace the four remaining substring checks with `isApiGroup(..., "cluster.x-k8s.io")`,
matching the exact-group pattern already used for CAPI `clusters` and the
CNPG/Velero collision handling.

`machines` and `machinesets` are in KNOWN_KINDS, so the strict guard on
its own would render a blank drawer for a foreign CRD. Add a
`capiCollisionFallthrough` that routes such kinds to GenericRenderer,
mirroring the existing `groupGatedFallthrough` for `clusters` etc.

Regression coverage in ResourceRendererDispatch.test.tsx uses a foreign
`extension.cluster.x-k8s.io/v1` CRD and asserts, at all three boundaries,
that a substring guard would have failed:
- renderer dispatch for `clusters`, `machines`, `machinesets`
- `getResourceStatus` for `clusters` and `machines`
- the topology-controlled warning banner
plus a positive check that a real `cluster.x-k8s.io` resource still gets
the warning.
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Use exact CAPI API-group matching in renderer dispatch

🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

• Enforces exact CAPI API-group checks for renderers, statuses, and topology warnings.
• Routes colliding foreign Machine CRDs to generic rendering instead of blank drawers.
• Adds regression coverage for foreign suffix groups and valid CAPI resources.
Diagram

graph TD
  A["K8s resource"] --> B{"Exact CAPI group?"} -- Yes --> C["CAPI renderer"] --> D["CAPI status"]
  B -- No --> G["Generic renderer"] --> H["Generic status"]
  C --> E{"Ownership label?"} -- Yes --> F["Topology warning"]
Loading
High-Level Assessment

The PR's approach is appropriate: it reuses the established isApiGroup helper at every independent CAPI boundary and adds an explicit generic fallback for known-plural collisions. Folding machines and machinesets into the broader groupGatedFallthrough table was considered, but the dedicated CAPI fallback keeps this focused fix easier to audit and consistent with other integration-specific collision guards.

Files changed (2) +107 / -5

Bug fix (1) +13 / -5
ResourceRendererDispatch.tsxRequire exact CAPI groups across dispatch guards +13/-5

Require exact CAPI groups across dispatch guards

• Replaces CAPI substring checks with exact API-group matching for Machine and MachineSet renderers, Machine status, and topology warnings. Adds a generic-renderer fallthrough for foreign machines and machinesets that would otherwise be suppressed as known kinds.

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx

Tests (1) +94 / -0
ResourceRendererDispatch.test.tsxCover CAPI API-group suffix collisions +94/-0

Cover CAPI API-group suffix collisions

• Adds regression tests proving foreign suffix-matching groups use generic rendering and status behavior for clusters, machines, and machinesets. Verifies topology warnings remain suppressed for foreign CRDs while genuine CAPI resources still receive them.

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (3) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. machinesets status lacks group guard 📘 Rule violation ≡ Correctness
Description
The renderer now routes foreign machinesets CRDs to GenericRenderer, but getResourceStatus()
still sends every resource with that plural through getMachineSetStatus(). This inconsistent
collision handling causes the drawer header and Diagnose health to interpret unrelated CRD phases
and conditions using CAPI semantics, potentially fabricating CAPI status.
Code

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[881]

+        {kind === 'machinesets' && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && <CAPIMachineSetRenderer data={data} onNavigate={onNavigate} />}
Relevance

●●● Strong

Status dispatch remains inconsistent with the PR’s exact-group fix and can fabricate CAPI health for
foreign machinesets CRDs.

PR-#1480

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 3036714 requires colliding CRD kinds to be group-guarded consistently across
renderer and status dispatch. The renderer requires the exact CAPI group at line 881, while the
status branch at line 1200 dispatches solely on the machinesets plural; getMachineSetStatus()
then applies CAPI phase and Ready-condition mappings that WorkloadView displays in the drawer
header and uses to derive Diagnose health.

Rule 3036714: Guard renderer, status, and actions when CRD kind collides with core/other CRDs
packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[879-881]
packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[1198-1200]
packages/k8s-ui/src/components/resources/resource-utils-capi.ts[11-48]
packages/k8s-ui/src/components/resources/resource-utils-capi.ts[241-246]
packages/k8s-ui/src/components/workload/WorkloadView.tsx[704-708]
packages/k8s-ui/src/components/workload/WorkloadView.tsx[800-804]
packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[1002-1013]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Foreign CRDs using the `machinesets` plural are rendered generically, but their status is still calculated by the CAPI-specific `getMachineSetStatus()` function.

## Issue Context
The renderer, status calculation, and warning banner are independent dispatch boundaries. Apply the same exact `cluster.x-k8s.io` group guard used by the renderer and the `machines` status branch to the MachineSet status branch, then add regression coverage proving that a foreign `machinesets` resource receives generic status while a real CAPI MachineSet retains CAPI status.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[1198-1200]
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[888-908]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Comment records previous behavior 📘 Rule violation ⚙ Maintainability
Description
The added test comment says the foreign group used to pass the old substring guard, explicitly
recording change history in source code. The rationale can be retained without describing the
previous implementation.
Code

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[R849-850]

+// A foreign group ending in `cluster.x-k8s.io` (e.g. `extension.cluster.x-k8s.io`)
+// used to pass a substring guard and inherit CAPI rendering, status, or the
Relevance

●●● Strong

Comment explicitly records prior implementation history, conflicting with repository comment rules;
removing it is a straightforward maintainability fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 3036538 prohibits explicit diff or change history in code comments. The new comment
states that the foreign group used to pass a substring guard.

Rule 3036538: Disallow references to tickets or PR history in code comments
packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[849-853]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The added comment records previous implementation behavior with the phrase `used to pass`.

## Issue Context
Keep the collision invariant and reason for exact-group matching, but phrase it in present-tense terms without referring to code history.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[849-853]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Comment restates status assertion 📘 Rule violation ⚙ Maintainability
Description
The comment immediately before expect(s?.text).toBe('Provisioned') only restates that the generic
getter returns the raw phase. It adds no rationale beyond the preceding explanation and assertion.
Code

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[871]

+    // The generic getter surfaces the raw phase text.
Relevance

●●● Strong

Comment merely restates the immediately following assertion and adds no rationale, matching the
stated maintainability rule.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 3036542 disallows comments that merely restate obvious code behavior. Line 871
describes exactly what the assertion on line 872 verifies, while lines 865-866 already provide the
relevant rationale.

Rule 3036542: Avoid explanatory comments that restate obvious code behavior
packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[865-872]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The comment directly narrates the immediately following status assertion without adding intent, constraints, or non-obvious context.

## Issue Context
The preceding comment already explains the distinction between generic and CAPI status handling, so this additional comment can be removed.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[865-872]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 41 rules
Review mode: ⚖️ Balanced

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

{kind === 'machines' && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && <CAPIMachineRenderer data={data} onNavigate={onNavigate} />}
{kind === 'machinedeployments' && <CAPIMachineDeploymentRenderer data={data} onNavigate={onNavigate} />}
{kind === 'machinesets' && data?.apiVersion?.includes('cluster.x-k8s.io') && <CAPIMachineSetRenderer data={data} onNavigate={onNavigate} />}
{kind === 'machinesets' && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && <CAPIMachineSetRenderer data={data} onNavigate={onNavigate} />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. machinesets status lacks group guard 📘 Rule violation ≡ Correctness

The renderer now routes foreign machinesets CRDs to GenericRenderer, but getResourceStatus()
still sends every resource with that plural through getMachineSetStatus(). This inconsistent
collision handling causes the drawer header and Diagnose health to interpret unrelated CRD phases
and conditions using CAPI semantics, potentially fabricating CAPI status.
Agent Prompt
## Issue description
Foreign CRDs using the `machinesets` plural are rendered generically, but their status is still calculated by the CAPI-specific `getMachineSetStatus()` function.

## Issue Context
The renderer, status calculation, and warning banner are independent dispatch boundaries. Apply the same exact `cluster.x-k8s.io` group guard used by the renderer and the `machines` status branch to the MachineSet status branch, then add regression coverage proving that a foreign `machinesets` resource receives generic status while a real CAPI MachineSet retains CAPI status.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[1198-1200]
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[888-908]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +849 to +850
// A foreign group ending in `cluster.x-k8s.io` (e.g. `extension.cluster.x-k8s.io`)
// used to pass a substring guard and inherit CAPI rendering, status, or the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Comment records previous behavior 📘 Rule violation ⚙ Maintainability

The added test comment says the foreign group used to pass the old substring guard, explicitly
recording change history in source code. The rationale can be retained without describing the
previous implementation.
Agent Prompt
## Issue description
The added comment records previous implementation behavior with the phrase `used to pass`.

## Issue Context
Keep the collision invariant and reason for exact-group matching, but phrase it in present-tense terms without referring to code history.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[849-853]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

apiVersion: FOREIGN_GROUP,
status: { phase: 'Provisioned' },
})
// The generic getter surfaces the raw phase text.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Comment restates status assertion 📘 Rule violation ⚙ Maintainability

The comment immediately before expect(s?.text).toBe('Provisioned') only restates that the generic
getter returns the raw phase. It adds no rationale beyond the preceding explanation and assertion.
Agent Prompt
## Issue description
The comment directly narrates the immediately following status assertion without adding intent, constraints, or non-obvious context.

## Issue Context
The preceding comment already explains the distinction between generic and CAPI status handling, so this additional comment can be removed.

## Fix Focus Areas
- packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx[865-872]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1db0a91. Configure here.

{kind === 'poolers' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGPoolerRenderer data={data} onNavigate={onNavigate} />}
{/* Cluster API (CAPI) */}
{'topology.cluster.x-k8s.io/owned' in (data?.metadata?.labels ?? {}) && data?.apiVersion?.includes('cluster.x-k8s.io') && (
{'topology.cluster.x-k8s.io/owned' in (data?.metadata?.labels ?? {}) && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Topology banner misses CAPI groups

Medium Severity

The ClusterClass warning now requires an exact cluster.x-k8s.io match, so topology-owned resources in official sibling groups such as controlplane.cluster.x-k8s.io and infrastructure.cluster.x-k8s.io no longer show it. Those groups previously matched the substring check and still receive topology.cluster.x-k8s.io/owned.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1db0a91. Configure here.

@hisco

hisco commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Thanks for the fix. We are closing this one.

Issue #1582 drew two independent fixes. #1587 was opened a day earlier and covers the same three dispatch points, so we took that one. Both patches change the same lines, so we could not take both. Sorry for the wasted effort.

Two notes on your patch, for next time.

  1. The exact group cluster.x-k8s.io is too strict for the "Topology-controlled" banner. Cluster API's topology controller labels objects in four groups: cluster.x-k8s.io, controlplane.cluster.x-k8s.io, infrastructure.cluster.x-k8s.io and bootstrap.cluster.x-k8s.io. The banner needs to accept all four. As written, a topology-owned KubeadmControlPlane would stop showing it.
  2. The machinesets branch in getResourceStatus() needs the same guard you added to the renderer. Without it, a MachineSet from another project gets a generic drawer and a Cluster API status badge at the same time.

You found a real bug here. We have other open issues if you want to pick one up.

@hisco hisco closed this Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix CAPI renderer guards to use exact API-group matching

2 participants