Skip to content

Open the exact object behind EndpointSlice, Knative and Crossplane reference links - #1996

Merged
nadaverell merged 10 commits into
mainfrom
feature/relationship-endpoint-identities
Oct 13, 2026
Merged

nadaverell merged 10 commits into
mainfrom
feature/relationship-endpoint-identities

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

EndpointSlice, Knative and Crossplane references can name a custom resource whose Kind collides with a core resource. Preserve the declared API group and discovered scope so clicking a reference opens the intended object rather than a same-named object in another API.

Changes

  • Resolve EndpointSlice targets with their own namespace and discovered scope. Keep controller-written Pod references without apiVersion navigable, and make nodeName a core Node link.
  • Resolve group-qualified plurals before group-blind Kind aliases, while protecting canonical core plurals against discovery collisions.
  • Use one Knative reference reader and link component for versioned and group-only declarations. Incomplete custom identities remain visible text.
  • Use the same group-qualified plural for Crossplane composed-resource status fetches and navigation.

Validation

Go build, pkg and internal tests, TypeScript checks and the complete shared UI suite pass (4,534 tests, one existing skip). Full frontend/embed/backend build passes. Web tests passed except one existing timer under concurrent load; all seven tests in that file passed on an isolated rerun.

Compared main and this change against the EKS nonprod and management test clusters using owned fixtures, REST, read-only MCP calls and headless Chromium. EndpointSlice and Knative links open the custom object rather than the core decoy. Crossplane custom Node status uses its discovered workerhosts plural and succeeds; its custom Endpoints alias already resolves on main and remains correct. Controller-shaped Pod and core Node clicks remain functional. Browser runs recorded no page errors.

The Knative fixture was not Ready, and Crossplane fixtures were manually created composite/managed-resource shapes; the evidence establishes identity and navigation, not successful controller reconciliation. Group-only Knative references are component-tested because the installed PingSource schema rejects that experimental field.

EndpointSlice exact destination
Knative sink destination

Notes

#2009 builds on this reference converter. The overlapping #2047 should follow this stack and retain these shared Knative helpers, collision protections and tests alongside its additional brokerRef changes. No endpoint or authorization behavior changes.

@nadaverell
nadaverell requested a review from hisco as a code owner October 7, 2026 04:13
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve EndpointSlice target identity and link endpoint Nodes

🐞 Bug fix âœĻ Enhancement 🧊 Tests 🕐 10-20 Minutes

Grey Divider

AI Description

â€Ē Preserve each endpoint target’s API group and namespace to prevent incorrect drawer links.
â€Ē Leave incomplete references as text and link endpoint placement to the core Node.
â€Ē Add navigation and renderer tests for scope collisions, cross-namespace targets, and unlinked
 references.
Diagram

graph TD
  Slice["EndpointSlice drawer"] --> Helper["Reference helper"] --> Scope{"Navigable identity?"} -->|yes| Target["Target drawer"]
  Scope -->|no| Text["Plain target text"]
  Discovery["API discovery"] --> Helper
  Builtins["Built-in scopes"] --> Helper
  Slice -->|nodeName| Node["Core Node drawer"]
Loading
High-Level Assessment

Using existing discovery and built-in scope data in a shared conversion helper fits this navigation-only change. Guessing scope risks incorrect links, while fetching scope on each click adds API reads without improving known-reference handling.

Files changed (4) +100 / -10

Bug fix (2) +37 / -10
EndpointSliceRenderer.tsxRender safe target links and linked Node placement +12/-10

Render safe target links and linked Node placement

â€Ē Uses the shared ObjectReference helper instead of borrowing the slice namespace for target navigation. Renders unresolved targets as text and makes nodeName open the cluster-scoped core Node.

packages/k8s-ui/src/components/resources/renderers/EndpointSliceRenderer.tsx

navigation.tsResolve ObjectReferences with exact identity and scope +25/-0

Resolve ObjectReferences with exact identity and scope

â€Ē Retains discovered namespace scope by API group and Kind and adds a reusable ObjectReference-to-ResourceRef helper. The helper falls back to known built-in scopes and refuses references missing the identity or namespace needed for safe navigation.

packages/k8s-ui/src/utils/navigation.ts

Tests (2) +63 / -0
EndpointSliceRenderer.test.tsxTest EndpointSlice target and Node navigation +35/-0

Test EndpointSlice target and Node navigation

â€Ē Adds a rendered-component test for a custom cluster-scoped target, a cross-namespace core Pod, and a core Node placement link. Confirms an incomplete target remains visible but unlinked.

packages/k8s-ui/src/components/resources/renderers/EndpointSliceRenderer.test.tsx

object-reference-navigation.test.tsCover reference scope and incomplete-identity safeguards +28/-0

Cover reference scope and incomplete-identity safeguards

â€Ē Tests explicit namespaces, core cluster scope, and colliding Kinds across API groups. Also verifies that missing identity or unresolved cluster scope prevents navigation and that resetting discovery clears learned scope.

packages/k8s-ui/src/utils/object-reference-navigation.test.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can turn these tips off under Display > Comment parts

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 9373bf5 🧠 Deep

Results up to commit a2172a7


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Qodo Logo

@nadaverell nadaverell changed the title Preserve EndpointSlice target API identity and link endpoint Nodes EndpointSlice drawer: open the exact target object and link endpoint Nodes Oct 9, 2026
nadaverell added a commit that referenced this pull request Oct 9, 2026
â€Ķry (#1997)

The Service drawer now lists the EndpointSlices that publish the
Service's endpoints, for every Service except ExternalName ones. On
`main` it lists them only for Services **without** a selector, which
hides them in the case where they explain the most: a Service whose
selector matches no Pods, or whose endpoints aren't ready.

## How it works

EndpointSlices aren't held in Radar's informer cache, so every read is a
direct API call. On `main`, each open drawer for a selector-less Service
polls a LIST of every EndpointSlice in the namespace every 30 seconds
and filters in the browser. That cost grows with the namespace, not the
Service.

This PR asks the server for exactly one Service's slices:

- `GET /api/services/{namespace}/{name}/endpointslices` returns `{items,
truncated}`.
- It's one LIST selected by the `kubernetes.io/service-name=<name>`
label. kube-proxy and the EndpointSlice controller use the same label,
so it's the contract for "this slice serves this Service".
- Capped at 500 slices. When more exist, `truncated` is set and the
drawer says so.
  - The selector is validated, so a name like `web,app=x` returns 400.
- **Access matches the existing list route.** It is gated like `GET
/api/resources/endpointslices?namespace=<ns>`: the same namespace clamp,
`--namespace-scope`, and per-user RBAC intersection. A namespace the
user can't read returns 403, not an empty list, so the drawer never says
"no EndpointSlices" to someone who simply can't see them.
- **Association is by label only.** A slice owned by the Service but
labelled for another Service is listed under the Service its label
names, because that's where kube-proxy sends its traffic.
- **Older Radars.** A new `serviceEndpointSlices` capability flag gates
the hook (`radarFeatures.ts`). Against a Radar without the endpoint
(Radar Hub with an older cluster), the drawer keeps `main`'s behaviour.
- `pkg/k8score` gains `ListDirectSelected` (additive). `ListDirect` is
unchanged.
- The EndpointSlice drawer's Service link carries the core API group
explicitly.

## Result on a live cluster

![kube-dns: no section on main, its slice with this
PR](https://github.com/user-attachments/assets/3447700d-975e-4e73-aebc-2269fd2878e4)

![A Service whose selector matches no Pods: its slices are now
visible](https://github.com/user-attachments/assets/acc7c94a-fda4-4f48-bb16-e3538fbd324c)

| Service | `main` | this PR |
|---|---|---|
| `kube-system/kube-dns` (selector) | no section | 1 slice, 2/2 ready |
| selector matching no Pods | no section | both labelled slices |
| no selector, 2 slices | 2 slices | same 2 slices |
| no slices at all | "No EndpointSlices found" | same |

Direct API checks: an unknown Service returns `[]`, and an injected
selector returns 400.

## Testing

- Handler tests cover:
- label selection, namespace and limit, checked on the actual LIST call;
  - truncation, and an empty result as `[]`;
  - a selector-injection name returning 400;
  - in auth mode, a namespace outside the user's access returning 403.
- `k8score` tests cover the selector, limit, continue token and field
stripping.
- Renderer tests cover selector and selector-less Services; the loading,
error, empty and truncated states; ExternalName; and the older-Radar
fallback.
- `make tsc`, `go test ./internal/server/... ./internal/k8s/...`,
`pkg/k8score`, and the shared UI and web suites pass.
- Live: the comparisons above, on a kind cluster.

## Notes

This and #1996 both add `EndpointSliceRenderer.test.tsx`. Whichever
merges second needs a one-file rebase.

ðŸĪ– Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@nadaverell nadaverell changed the title EndpointSlice drawer: open the exact target object and link endpoint Nodes Open the exact object behind EndpointSlice, Knative and Crossplane reference links Oct 9, 2026
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
The EndpointSlice controller writes Pod targets without apiVersion, so the
strict identity rule turned every controller-managed target link into plain
text. An omitted apiVersion now means the core group only for a built-in core
Kind; custom kinds without apiVersion still stay unlinked. Tests are rewritten
in the renderer-test style.
Kind-only navigation used whichever API group discovery listed last, so a CRD
that declares Kind Node under another plural could take over core Node and Pod
links. Core resources now keep their plural for kind-only lookups, matching
the existing first-wins rule for plurals. Collisions between CRDs keep the
previous behavior.
The server forwards partial discovery, so core protection seeded only from
discovered entries vanished when the core group was missing. Seed it from the
built-in core table first.
A CRD may register a core Kind's lowercase word (node) as its own plural, which
the already-plural shortcut accepted for core Node links. An exact PascalCase
core Kind now resolves to its built-in plural first; lowercase plural slugs and
non-core special cases such as PodGroup keep their behavior.
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
â€Ķdpoints targets

Core Kinds now keep their core plural in group-blind lookups, so Crossplane
references, which can reuse any core Kind, pluralize within their own group.
An Endpoints target without apiVersion is core, like Pod and Node.
â€Ķt apiVersion

Knative sinks, subscribers, replies and channels link within their reference's
API group, so a Knative Service sink no longer opens the core Service. An
ObjectReference without apiVersion is core only for an exact core Kind; a
resource name such as Services is not one.
A DomainMapping to a Knative Service no longer opens the core Service.
Knative references can identify a group without a version. Resolve those references through the shared exact-reference component, leaving incomplete identities as text instead of routing to a core object. Endpoint labels use the normalized target scope so they describe the object their link opens.
@nadaverell
nadaverell force-pushed the feature/relationship-endpoint-identities branch from b0da042 to 9373bf5 Compare October 13, 2026 02:26
@nadaverell

Copy link
Copy Markdown
Contributor Author

/review

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9373bf5

@nadaverell
nadaverell merged commit 340bb85 into main Oct 13, 2026
14 of 15 checks passed
@nadaverell
nadaverell deleted the feature/relationship-endpoint-identities branch October 13, 2026 10:31
nadaverell added a commit that referenced this pull request Oct 14, 2026
â€Ķes (#2009)

An HPA can scale a Deployment using an Object metric from an Ingress,
but Radar's metric card currently gives no route to that source. KEDA
ScaledObjects targeting StatefulSets also lose their topology edge
because targets are resolved before StatefulSet nodes exist.

## Design

- Show each HPA Object source inside its existing metric card,
separately from the workload being scaled. Carry the source's API group,
selector and scope, and pair observations by structured metric identity.
- Preserve declared target API identity for HPA, VPA and KEDA links
using the shared reference converter. VPA keeps one Target link and
ordinary versionless built-in navigation.
- Apply KEDA's documented apps/v1 Deployment defaults consistently in
the UI, topology, missing-reference detection and compact context.
Resolve target edges after supported workload nodes exist, retaining
uses edges and scaleTarget/scaledBy projections.

No endpoint, read privilege or custom scale-subresource graph support is
added. Merge after #1996, which supplies exact-reference navigation.

## Testing

TypeScript, the complete Go pkg suite, both focused topology regressions
and all 19 autoscaler renderer tests pass. Component regressions
exercise an Ingress Object source, exact declared groups and versionless
VPA links; builder regressions exercise KEDA StatefulSet edges and both
relationship projections. The documented KEDA setup fails against main
and passes with this change in a synthetic builder experiment.

Merge verification remains blocked: full frontend runs reported failures
in unchanged tests under severe host contention, including the known
kind-view flake, which also failed in isolation. Root Go build/internal
tests, full application build and lint did not complete; rerun those
checks before merging. Assertions and timeouts were not weakened.

The supplied clusters have ten Resource CPU HPAs and no KEDA or VPA
CRDs. Historical live HPA fixtures validated API admission and source
navigation. No custom-metrics adapter or KEDA controller reconciled
them; source links do not establish working metrics or successful
scaling.

![Historical synthetic HPA Object-source
navigation](https://github.com/user-attachments/assets/9c840af9-a840-4796-9b5e-327a0598a6c5)
![Historical synthetic Namespace Object-source
navigation](https://github.com/user-attachments/assets/4d5f6b78-f8f7-459a-97f8-5785fedc1bb3)
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.

1 participant