Skip to content

Connect certificates to cached issuers - #1979

Merged
nadaverell merged 2 commits into
mainfrom
feature/relationship-certificate-issuers
Oct 13, 2026
Merged

nadaverell merged 2 commits into
mainfrom
feature/relationship-certificate-issuers

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Certificates can reference a working Issuer or ClusterIssuer that has no owner-connected topology node. Join built-in issuer references to exact cached objects so the existing Depends On / Required By views and REST/MCP context can navigate the issuance dependency.

The resolver applies cert-manager's omitted-kind/group defaults, lists each target kind once and materializes only observed referenced issuers. Missing targets and same-name objects in another namespace or API group do not create links. ClusterIssuer nodes carry their exact cluster authorization tuple, and already-seeded issuers retain their metadata owner without duplicate nodes or edges. No new endpoint or permission grant is introduced.

Validation: the complete Go, type-check and frontend matrix passes at the final head. A fresh embedded build and live main/PR REST/MCP comparisons with cert-manager 1.20.2 verify both issuer directions, missing-target rejection and preservation of the Issuer's Deployment owner. Browser captures of both drawers are below.

Certificate issuer dependency
Issuer: Required By the Certificate

A cold target kind may initialize an informer; the resolver performs no per-certificate object Gets. External issuer groups remain out of scope.

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

Copy link
Copy Markdown

PR Summary by Qodo

Connect certificates to observed cert-manager issuers

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Resolve certificate issuer references from cached Issuer and ClusterIssuer inventories, including
 defaulted references.
• Add only observed, referenced issuers while preserving namespace, group, and cluster-scoped
 permission identity.
• Test resolution and visibility behavior, and document the resulting topology.
Diagram

graph TD
  Cert["Certificate"] --> Resolver["Reference resolver"] --> Cache["Issuer inventories"] --> Match{"Observed match?"} -->|Yes| Graph["Topology dependency"] --> Filter["Permission filter"]
  Match -->|No| Skip["No issuer link"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Fetch each referenced issuer individually
  • ➕ Avoids listing unreferenced issuer objects.
  • ➖ Introduces per-certificate lookups and potentially substantial API traffic.
  • ➖ Does not use the existing cached per-kind inventory.

Recommendation: Keep cached per-kind listing: it bounds issuer lookups to one list per available kind and permits exact identity checks before adding nodes. Per-certificate fetching offers little benefit here relative to its lookup cost.

Files changed (4) +186 / -33

Bug fix (2) +90 / -32
builder.goDelegate certificate issuer linking to cached resolution +2/-32

Delegate certificate issuer linking to cached resolution

• Replaces the existing-node-only issuer edge loop with the new resolver and propagates its lookup warnings.

pkg/topology/builder.go

certificate_issuers.goResolve and link observed built-in issuers +88/-0

Resolve and link observed built-in issuers

• Lists available issuer kinds through the dynamic provider, applies default kind and group, and matches references by exact group, kind, namespace, and name. Creates nodes only for referenced observed issuers and records the dynamic permission tuple on new ClusterIssuer nodes.

pkg/topology/certificate_issuers.go

Tests (1) +95 / -0
certificate_issuers_test.goTest cached issuer joins and permission filtering +95/-0

Test cached issuer joins and permission filtering

• Covers default and cluster issuer references, collisions, missing and shared targets, lookup failures, and denied cluster-scoped visibility. Asserts one list per issuer kind and no per-certificate object fetches.

pkg/topology/certificate_issuers_test.go

Documentation (1) +1 / -1
integrations.mdClarify cert-manager issuer topology behavior +1/-1

Clarify cert-manager issuer topology behavior

• Documents links to observed, referenced built-in issuers, cert-manager reference defaults, external-group exclusions, and cluster-scoped visibility.

docs/integrations.md

@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) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Referenced issuers lose owner links ✓ Resolved
Description
addCertificateIssuerEdges adds a referenced issuer before addGenericCRDNodes, whose
existing-resource check then skips that issuer without creating its owner edges. When a referenced
issuer has an owner reference to a node already in the topology and generic CRD support is enabled,
its owner-to-issuer relationship is absent.
Code

pkg/topology/certificate_issuers.go[R82-83]

+			nodes = append(nodes, Node{ID: target, Kind: NodeKind(kind), Name: name, Status: extractGenericStatus(issuer), Data: data})
+			byResource[key] = target
Evidence
The new pass appends the issuer node before the generic pass runs. The generic pass skips candidates
whose exact resource identity is already present, and its owner edges are generated only for
candidates it adds.

pkg/topology/builder.go[5769-5775]
pkg/topology/builder.go[8874-8879]
pkg/topology/builder.go[8927-8953]
pkg/topology/certificate_issuers.go[75-85]

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

## Issue description
Adding a referenced issuer before the generic CRD pass causes that pass to skip the issuer and omit its owner-reference edges.
## Fix Focus Areas
- pkg/topology/certificate_issuers.go[75-85]
- pkg/topology/builder.go[8874-8879]
- pkg/topology/builder.go[8927-8953]
## Recommended Fix
Preserve owner-reference edge creation for issuer nodes already added by the certificate pass. Reuse the generic pass's exact-resource owner resolution and avoid adding a duplicate issuer node or edge.

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


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

Qodo Logo

Comment thread pkg/topology/certificate_issuers.go

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

Reviewed by Cursor Bugbot for commit 3b78585. Configure here.

Comment thread pkg/topology/certificate_issuers.go
nadaverell added a commit that referenced this pull request Oct 9, 2026
…ationships (#1978)

Radar's relationship view treated every `uses` edge in the graph that
wasn't storage as autoscaling. A Certificate that uses its Issuer would
be shown with the Issuer as its **Scale Target**, and the Issuer would
list the Certificate as its **Autoscaler**, in the drawer, the REST
resource details and the MCP resource context given to AI tools. This
separates real autoscalers from other dependencies, so those surfaces
say "depends on" when something isn't scaling.

Nothing changes visibly on today's `main`: no edge there is
misclassified yet. The edges this fixes arrive in follow-up PRs
(Certificate → Issuer in #1979, KEDA → TriggerAuthentication in #1987),
which build on this projection.

## What changed

**Classification of `uses` edges** (`pkg/topology/relationships.go`).
Each edge is put into exactly one group:

| Edge source | Group |
|---|---|
| core `PersistentVolumeClaim` | storage (`storageRefs` / `consumers`),
as before |
| `HorizontalPodAutoscaler` (`autoscaling`), `VerticalPodAutoscaler`
(`autoscaling.k8s.io`), `ScaledObject` / `ScaledJob` (`keda.sh`), same
namespace | autoscaling (`scaleTarget` / `scalers`), as before |
| anything else | **new** `dependencies` / `dependents` |

- Matching uses the exact API group, so a custom resource that happens
to be named `ScaledObject` or `PersistentVolumeClaim` is no longer
treated as one.
- A KEDA scaler's TriggerAuthentication is a dependency, not its scale
target, so it can't displace the workload the scaler actually scales.

**New fields, additive.** `Relationships` and `resourceContext` gain
`dependencies` and `dependents`. In resource context they're filtered by
the caller's permission to read each target, and dropped ones are
reported in the existing omission reasons. `docs/mcp.md` describes how
they differ from `scaledBy`.

**UI.** The Related Resources section shows them as **Depends On** and
**Required By**, with the same badges, navigation and limits as its
other groups.

**Radar Hub compatibility.** Hub embeds a versioned UI that doesn't know
the new fields. Issuer and KEDA-authentication dependencies are
therefore also reported in the existing `configRefs` / `consumers`
(Configuration / Used By), so Hub keeps working links. The new UI hides
those mirrored entries, so each target appears once.

**De-duplication.** Reference de-duplication across these groups uses
the existing group-aware key from Checks (`resource-refs.ts`), so
same-named resources from different API groups stay distinct.
`configRefs` lists each target once on every path. Each list is
de-duplicated once after it is collected, so a PVC shared by thousands
of workloads stays linear rather than rescanning the list per consumer.

No new endpoint, permission, graph edge type, or SSE/neighborhood
change.

## Result on a live cluster

To show the fix, both builds include #1979's Certificate → Issuer edges
on top of `main`. "Before" uses `main`'s version of the projection,
"after" uses this PR's. Real cert-manager resources:

![Certificate: Scale Target becomes Depends
On](https://github.com/user-attachments/assets/384affd1-f486-4b27-b4e0-0cc968a13114)

![Issuer: Autoscaler becomes Required
By](https://github.com/user-attachments/assets/c6008433-a1d1-443d-ba59-ef81006bdcb4)

On `main` alone, this branch (merged with `main`) returned relationships
identical to `main` for all 79 resources on the test cluster, including
HPA, VPA, KEDA and PVC fixtures.

## Testing

- Graph fixtures cover Certificate/Issuer direction, KEDA target plus
authentication, PVC consumers, exact-group collisions, one configuration
target reached by two edges, each autoscaler kind accepted only in its
own group and namespace, and a claim shared by 20,000 Jobs plus a
duplicate edge (each consumer listed once).
- Context tests cover target authorization and omission reasons.
Rendered-component tests cover dependency-only responses, the
directional labels, exact-group navigation and the mirrored projections.
Existing Checks identity tests confirm unchanged keys.
- `go test` for `pkg/topology`, `pkg/resourcecontext`, `internal/server`
and `internal/mcp`; `make tsc`; the affected shared UI suites (22
tests).
- Live: the comparisons above, on a kind cluster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feature/relationship-dependency-projection to main October 9, 2026 23:17
nadaverell added a commit that referenced this pull request Oct 10, 2026
…ling (#2048)

Topology's `uses` edges no longer mean only "autoscales". They also draw
mounted claims and declared dependencies, such as Certificate → Issuer,
Kustomization → its source, and Gateway → GatewayClass. Two places in
the UI still read every `uses` edge as autoscaling: the graph legend,
and the timeline's lane nesting.

## What changed

**Timeline nesting.** The timeline made the target of every `uses` edge
its source's parent lane. That's right for an autoscaler or a mounted
claim, which sit under the workload they serve. For a dependency it
files every dependent under one shared lane: every Certificate under its
ClusterIssuer, and Gateways from different namespaces under one
GatewayClass. Only HorizontalPodAutoscalers, VerticalPodAutoscalers,
KEDA ScaledObjects and ScaledJobs, and PersistentVolumeClaims, matched
on exact API group, now nest under their target.

**Graph legend.**
- Pink edges were labelled "scales"; they now read "uses".
- Gray `manages` edges were labelled "owns", though they also draw
GitOps inventory; they now read "manages".
- Policy and disruption-budget (`protects`) edges had no color of their
own and rendered in that same gray. They're now violet, with their own
legend entry.

## Testing

- A timeline test with an HPA → Deployment edge and a Certificate →
Issuer edge. The autoscaler nests, the dependency doesn't. It fails with
the old rule.
- Topology component tests, `make tsc`, and the shared UI and web suites
pass.

## Notes

The open dependency PRs (#1979, #1986, #1989, #1993) add `uses` edges
from dependents to their dependencies. This PR makes the legend and
timeline correct for them, so it should land before or with them.
Resolve declared certificate issuers from cached exact identities so ownerless issuers and seeded resources keep their dependency and ownership links.
Resolve declared certificate issuers from cached exact identities so ownerless issuers and seeded resources keep their dependency and ownership links.
@nadaverell
nadaverell force-pushed the feature/relationship-certificate-issuers branch from 5a684b7 to 3958943 Compare October 13, 2026 03:26
@nadaverell
nadaverell merged commit 4be4a8d into main Oct 13, 2026
9 checks passed
@nadaverell
nadaverell deleted the feature/relationship-certificate-issuers branch October 13, 2026 09:14
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