Repository navigation
Per-resource health with provenance when Argo CD doesn't persist it - #1743
Conversation
…t persist it Argo CD 3.0 stops writing per-resource health into the Application (status.resourceHealthSource: appTree), so every reader of status.resources[].health went blind: no per-resource Issues, no cause bridge, Unknown tree nodes for non-topology kinds. Health on a tree node now carries where it came from (controller vs Radar) plus the reason/message/severity behind it, and every surface (node colour, Changes category, per-resource Issue, summary counts) reads that one resolved value. In appTree mode the host overlays Radar's issues engine finding on declared nodes that have none — problems only, never Healthy, in-cluster destinations only — and the UI marks those rows as Radar's with a notice above the Issues band. Remote-destination apps derive nothing and say so; standalone Radar adds a Radar Cloud pointer. The gitops-demo installs Argo CD 3.x by default with a fixture app that shows every branch: a CRD Argo evaluates, one it ignores, and a crashlooping Deployment.
…Radar's topology read A remote-destination Application declares resources that live on another cluster; matching them against this cluster's topology or cache attributed a same-named local object's health, metadata and ownership to them. Declared nodes of a remote app now carry only what the CR says. In appTree mode the issues-engine overlay also applies to nodes whose health is Radar's own topology read (Progressing at 1/2 replicas): the classified finding is the more specific answer. Controller health stays final. The demo script patches argocd-cm with a YAML patch file instead of JSON-escaping the Lua.
…alls Argo CD server-side Changes rows for a remote-destination app no longer read this cluster's objects for drift diffs or events, and the insights handler skips the live prefetch for them. Argo CD 3.x CRDs exceed the client-side last-applied annotation limit, so the demo applies install.yaml server-side.
…n a Healthy app; remote-destination fixture
Reworded to say what the user sees ('its resources aren't visible from
here') and what to do about it. 'Radar Cloud' now opens the same funnel
dialog as the top-right button instead of linking out, and the hint only
renders when that button does (cloudConnect capability present, not
embedded).
…iew in Cloud, not here
PR Summary by QodoRestore Argo CD 3 resource health with source provenance
AI Description
Diagram
High-Level Assessment
Files changed (37)
|
Code Review by Qodo
1.
|
…nings; docs link that exists The degraded-resources count and the Warning-event lead no longer stack on a per-resource finding of any severity. A Radar topology read with no engine reason is an observation, not a verdict: warning tier, and no Issue at all when Argo calls the app Healthy. The notice's Why link points at Argo's own 3.0 upgrade note until radar-docs carries the section.
… page's requests The tree and insights requests of one page load (and their 2s polls while a sync runs) each composed the cluster's issues. One 5s memo per user and namespace set now serves both; cleared on context switch with the other post-switch caches.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a28e1e6. Configure here.
…, with or without a token (#1751) Fast-follow to #1743. On Argo CD 3 the Application object no longer carries per-resource health, so Radar showed its own read of each resource, labelled as Radar's. argocd-server still has Argo's verdicts in its own cache. When Radar can reach it — with the integration connected, or with no setup at all on installs that serve reads anonymously — it now shows those instead, and the page looks exactly like it does on Argo CD 2.x: Argo's per-resource health, no notice, no "Found by Radar" markers. When it can't, the page says why and how to get there. ## What changes **Argo's verdicts from argocd-server.** For an Application in `appTree` mode that deploys to this cluster, Radar asks the Argo CD API server for the Application (`GET /api/v1/applications/{name}`, no refresh) to establish identity and mode, then reads per-resource health from its `resource-tree`. The tree is the source rather than the GET's own inlined health because Argo CD 3.0's GET-side inference keys the tree cache by bare app name, which can hand an Application outside Argo's namespace a same-named app's verdicts; the tree is scoped correctly on every 3.x. The answer is accepted only when its UID matches the local Application, so a different Argo install serving a same-named app is ignored. **Argo's answer is the whole answer.** When it arrives, declared nodes take Argo's health with `healthSource: controllerApi`, Radar's topology fill is cleared for kinds Argo has no check for, the issues-engine overlay doesn't run, and the notice doesn't show. `resourceHealthFromApi` on the insights summary and `healthFromApi` on the tree tell consumers so. **With a token, or without one.** With the Argo CD integration connected (Settings → Argo CD), the existing client and token are used. With nothing configured, this read path runs the existing argocd-server discovery and asks without credentials. A client that carries no token skips TLS verification, so the probe and the credential-free read reach the default in-cluster argocd-server with its self-signed certificate; every client that sends the token still honours `argoCdInsecureTls`, and a certificate the token-bearing client can't verify is reported with that setting named. Radar never sends credentials it wasn't given; refusals are cached per app for 15s, and discovery or a failed connect is throttled to once per retry interval, so a polling detail page never hammers an install that says no. **A tokenless connection counts only once it answers.** A reachable argocd-server that refuses reads is not an integration: it stays "Not connected" in Settings and never lights the diff. Once an anonymous read has succeeded (`users.anonymous.enabled` with a default role that can read applications), that connection *is* the integration — Settings shows "Connected · no token needed" with a line explaining when a token or URL still matters, and the Git-rendered diff and revision metadata work over it too. **A configured connection that doesn't deliver says so.** With the integration set up and no usable answer — token rejected, requires a token, server unreachable, request timed out, a different install answering, unknown app, or anything else — the insights summary carries the reason (`resourceHealthApiError`) and the notice leads with it: "Radar couldn't read Argo's per-resource health from your Argo CD server: the server couldn't be reached. Check Argo CD in Settings." The Argo CD status carries the same failure in plain words, so the Settings section the notice links to shows what's wrong. **The way out, from the page.** When Radar found nothing to mark on a Degraded app, the notice offers connecting Radar to argocd-server first and the `persist` knob second. The Settings clause is host-injected through the layout's `onOpenSettings`: standalone Radar renders it as a link that opens the Argo CD section (and focuses the token field when nothing is set up); an embedded host without that page gets only the Argo-side knob. Connecting in Settings refreshes the GitOps queries, so the page the user came from shows Argo's verdicts when they get back. The Argo CD Settings section names per-resource health alongside the diff. **Docs.** `docs/gitops.md` describes the three health sources and the connection semantics; radar-docs counterpart is skyhook-dev/radar-docs#95, to merge together. ## Testing - `go test` in both modules (client; manager: cache, negative cache, throttle for unconfigured and configured failures, anonymous-connection gating, TLS modes, probe-failure reporting; overlay: identity, mode, remote, clearing Radar fill, failure carrying, reason phrasing; status endpoint), `make tsc`, k8s-ui vitest (3684). - Live on a kind cluster with Argo CD v3.5.2: - nothing configured, anonymous disabled → 401 → Radar's read with markers, Settings "Not connected", no managed-resources calls; first open 0.22s from a laptop (port-forward + probe), repeats ~1ms. - nothing configured, `users.anonymous.enabled` + `policy.default: role:readonly` → Argo's verdicts with `controllerApi` (Widget Degraded via its Lua check with Argo's message, Deployment Progressing, Gadget no verdict), no notice, Settings connected + anonymous, diff capability lit. - integration connected with a token → same verdicts, no notice, no markers. - configured URL unreachable → notice with "the server couldn't be reached" and the Settings link landing on the Argo CD section; first open 0.05s, repeats throttled. - Anonymous negative case against a real Argo CD 3.2.6 install with defaults: 401, not connected. - Plain-GET vs resource-tree behaviour verified from Argo CD source (v3.0.0 vs v3.2.6 `inferResourcesStatusHealth`). - Visual: notice (error and findings states) and the Argo CD Settings section rendered and checked on a dev build. ## Notes - The anonymous connection is discovered by the Argo CD 3 health read, so on an Argo CD 2.x install (inline health) nothing triggers it and the diff still needs a token there — by design for now; making the capabilities probe try anonymously would open a port-forward on every app open for every unconfigured user. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds outbound calls to argocd-server on GitOps detail (including unconfigured anonymous discovery) and changes when the integration counts as connected; core auth paths are extended but bounded by caching, UID checks, and existing probe guards. > > **Overview** > On **Argo CD 3** (`appTree` mode), Radar can now show **Argo’s per-resource health from argocd-server** instead of defaulting to Radar’s own cluster read. The GitOps detail path calls **`ApplicationHealthCached`**: GET the Application for UID/mode, then **`resource-tree`** when health isn’t persisted on the CR (tree is authoritative in appTree; mis-scoped GET inference is not used as fallback). Successful answers overlay the tree with **`controllerApi`** health, skip the Radar issues overlay, and hide the “Found by Radar” notice. > > The Argo CD manager also supports **credential-free reads** when nothing is configured: discover argocd-server, probe/read without a token (tokenless clients skip TLS verify for self-signed in-cluster certs). A reachable server that **401s is not “connected”**; after a successful anonymous read, Settings shows **“Connected · no token needed”** and diff/revision metadata work like a normal integration. Health fetches are **15s cached** (including failures), with **throttled retries** so 2s polling doesn’t hammer refusals or failed discovery. > > When integration is configured but health can’t be fetched, the UI surfaces **`resourceHealthApiError`** and a notice with **Settings** remediation; anonymous/unconfigured failures fall through to Radar’s read quietly. Settings gains probe **failure reasons**, anonymous status, token focus on connect, and **GitOps query invalidation** after saving Argo CD config. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 94f71ca. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Argo CD 3.0 stopped persisting per-resource health into the Application object by default (
status.resourceHealthSource: appTree). Every Radar reader ofstatus.resources[].healthwent blind on such installs: no per-resource Issues, no cause bridge to the issues engine,Unknowntree nodes for non-topology kinds, no Degraded/Missing categories in the Resources table. The app-level badge still said Degraded with nothing under it. #1624 surfaced this.What changes
One resolved health per managed resource, with provenance. A tree node's
healthnow carrieshealthSource(controllerfor the GitOps CR's own verdict,radarfor Radar's read) plushealthReason/healthMessage/healthSeverity. Every surface reads that one value: node colour (previously from Radar's topology status even when Argo said Degraded), the Resources-table category, per-resource Issues, and the summary counts.argoResourceChangestakes health from the tree instead of re-reading the CR, and the tree and insights endpoints share oneresolveGitOpsTreepath so a node can't differ between the two responses. The wire fieldhealthstays a string, so@skyhook-io/k8s-uiconsumers are unaffected.Radar's diagnostics fill the gap, labelled as Radar's. In appTree mode, for an app that is not Healthy and deploys to this cluster, the host overlays the issues engine's existing classification (crash loops, image pulls, failed Ready conditions; already transient-filtered and owner-collapsed) onto declared nodes. Problems only, never
Healthy; controller health is final; a node whose health was only Radar's topology read gives way to the engine's more specific finding. The composition is memoized per user and namespace set for 5s (gitopsIssuesMemo) so a page load's two requests and the 2s polls share one pass.Severity follows the evidence. An engine finding keeps its own reason and severity as a per-resource Issue. A Radar topology read with no classified reason is a warning-tier observation, not a verdict, and neither it nor the "N managed resources are degraded" count appears on an app Argo calls Healthy. The count and the Warning-event lead from #1624 only show when no per-resource finding already names a resource.
The UI says so. A notice above the Issues band: "Argo CD 3 no longer records health for each resource on the application. Rows marked "Found by Radar" are problems Radar detected by looking at the resources itself." with a "Why?" link to the docs. When Radar's own look found nothing to mark, the notice says that and names the way out (
controller.resource.health.persist: "true"inargocd-cmd-params-cm). Rows and declared nodes carry a "Found by Radar" marker with the engine's message in the tooltip.Remote-destination apps read nothing local. An Application deploying to another cluster used to be matched against same-named local objects (topology, cache, drift, events, cause bridge). It now carries only what the CR says, with a notice: "This application deploys to a different cluster, so its resources aren't visible from here." Standalone Radar, when the Cloud funnel is available, adds "Connect both clusters to Radar Cloud to see this app's resources together.", where "Radar Cloud" opens the existing funnel dialog.
Fixture.
gitops-demoinstalls Argo CD v3.5.2 by default (server-side apply;ARGOCD_VERSION=v2.13.2selects the older inline shape). Newargo3-healthapp sourced from this repo (ARGO3_FIXTURE_REVISIONto test a branch): a CRD Argo evaluates via a Lua health check installed intoargocd-cm, one it ignores, and a crashlooping Deployment.guestbook-remotecovers the remote-destination case.Docs.
docs/gitops.md"Per-resource health"; one-line rule in CLAUDE.md; demo README matrix. The in-product "Why?" link targets/features/gitops#per-resource-healthon radar-docs, added by skyhook-dev/radar-docs#94 — merge that before the release that carries this.Not in this PR: sourcing Argo's own verdicts from the argocd-server API when the integration is connected (fast-follow), and
Missingderivation for declared-but-absent resources.Testing
go test ./...in both modules,make tsc, k8s-ui vitest (108).argo3-health(notice, engine-sourced headline, "Found by Radar" on the three declared nodes and in the Resources table, none on generated ReplicaSet/Pod; light and dark) andguestbook-remote(notice; the Cloud sentence opening the funnel dialog withRADAR_CLOUD_FUNNEL=on).Note
Medium Risk
Changes how GitOps detail surfaces health and issues for Argo CD 3 and remote apps—operator-facing diagnosis paths—but rules limit false positives (no synthetic Healthy, skip remote/healthy-app overlay) and coverage is heavy on unit tests.
Overview
Adds per-resource health with provenance for Argo CD 3’s default (
appTree), where the Application CR no longer carriesstatus.resources[].health. Tree and insights now shareresolveGitOpsTree, which canoverlayRadarHealthfrom the issues engine (problems only, memoized ~5s per user/namespaces) so tree nodes, Resources table, and Issues stay aligned viahealthSource(controller|radar) plus reason/message/severity.Insights and tree behavior change accordingly:
argoResourceChangesresolves health from the tree (not a second CR read); per-resource Issues respect engine severity and suppress contradictions when Argo calls the app Healthy; remote-destination Applications skip local topology/drift/events/cause overlay. The tree builder tagshealthMode/remoteDestinationand stops attributing local objects to spoke destinations.UI adds a banner (
GitOpsHealthSourceNotice), “Found by Radar” markers on tree/Changes rows, docs links, and standalone Radar Cloud hint for remote apps (openCloudFunnel). Demo/docs default to Argo v3.5.2 withargo3-healthandguestbook-remotefixtures.Reviewed by Cursor Bugbot for commit 7c58e43. Bugbot is set up for automated code reviews on this repo. Configure here.