Skip to content

Commit c4d4efb

Browse files
authored
Add GitOps application view (#543)
1 parent fc3ff94 commit c4d4efb

120 files changed

Lines changed: 17870 additions & 289 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 26 additions & 56 deletions
Large diffs are not rendered by default.

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,22 @@ test-e2e:
141141
test-chart:
142142
./scripts/test-chart.sh
143143

144+
# Bootstrap a kind cluster pre-loaded with curated GitOps scenarios
145+
# (Argo CD + Flux + healthy/suspended/app-of-apps/ApplicationSet/etc).
146+
# Useful for visual-testing GitOps UI changes against realistic state.
147+
# See scripts/gitops-demo/README.md for the full coverage matrix.
148+
gitops-demo:
149+
./scripts/gitops-demo.sh up
150+
151+
gitops-demo-down:
152+
./scripts/gitops-demo.sh down
153+
154+
gitops-demo-status:
155+
./scripts/gitops-demo.sh status
156+
157+
gitops-demo-drift:
158+
./scripts/gitops-demo.sh drift
159+
144160
# Run linter
145161
lint:
146162
go vet ./...

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,20 @@ View TLS certificate details and expiry dates across all namespaces — catch ex
235235

236236
### GitOps
237237

238-
Monitor and manage FluxCD and ArgoCD resources with unified status views and actions.
239-
240-
- **FluxCD**: GitRepository, OCIRepository, HelmRepository, Kustomization, HelmRelease, Alert
241-
- **ArgoCD**: Application, ApplicationSet, AppProject
242-
- Real-time sync status, health indicators, and reconciliation countdowns
243-
- Trigger reconciliation, suspend/resume resources, and view managed resource inventory
244-
- Problem detection with clear alerts for degraded or out-of-sync resources
245-
- **Note**: Topology connections between GitOps resources and managed workloads only appear when both are in the same cluster. FluxCD typically deploys to its own cluster. ArgoCD often manages remote clusters — connect Radar to the target cluster to see workloads, or to the ArgoCD cluster to see Application status.
238+
Monitor, diagnose, and manage FluxCD and ArgoCD resources from a dedicated GitOps workspace.
239+
240+
<p align="center">
241+
<img src="docs/screenshots/gitops-view.png" alt="GitOps fleet view" width="800">
242+
<br><em>GitOps fleet view — Argo + Flux applications side-by-side with sync, health, source, destination, and lifecycle state</em>
243+
</p>
244+
245+
- Fleet view + per-app detail page (Topology / Changes / Activity tabs) for **ArgoCD** (`Application`, `ApplicationSet`, `AppProject`) and **FluxCD** (`GitRepository`, `OCIRepository`, `HelmRepository`, `Bucket`, `Kustomization`, `HelmRelease`, `Alert`)
246+
- **Diagnosis pipeline** — field-level drift, recent events per resource, stuck-drift-loop detection, parsed operation-failures, structured one-click remediation
247+
- **Lifecycle awareness**`Terminating` chip replaces stale Sync/Health badges; severity ramps with deletion age; mutating ops refuse on zombies
248+
- **Cross-linked from the rest of Radar**`Managed by` chip in resource drawers, GitOps routing from Topology + Timeline + Helm view, `Consumed by` panel on Flux source CRs
249+
- **MCP integration**`manage_gitops` exposes sync / suspend / resume / reconcile / rollback with lifecycle-aware refusal
250+
251+
See the [GitOps guide](docs/gitops.md) for the full feature matrix, RBAC requirements, demo cluster, and single-cluster scope notes.
246252

247253
### Traffic
248254

docs/gitops.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# GitOps (Argo CD & Flux)
2+
3+
Radar's GitOps workspace gives Argo CD and Flux first-class treatment. Instead of treating Applications and Kustomizations as generic CRDs, you get a typed fleet view, a per-app detail page that diagnoses *why* something is misbehaving, and the controls you'd otherwise reach for `argocd` / `flux` CLI to run.
4+
5+
The hard part of GitOps tooling isn't sync — it's diagnosis. Radar surfaces drift, recent events, controller-failure attribution, and lifecycle state inline so you don't have to context-switch between `kubectl get`, `argocd app diff`, controller logs, and a YAML viewer to understand a stuck reconcile.
6+
7+
<p align="center">
8+
<img src="screenshots/gitops-view.png" alt="GitOps fleet view" width="800">
9+
<br><em>Fleet view — Argo + Flux applications side-by-side with sync, health, source, destination, and lifecycle state</em>
10+
</p>
11+
12+
## Supported CRDs
13+
14+
| Tool | Kinds |
15+
|------|-------|
16+
| **FluxCD** | `GitRepository`, `OCIRepository`, `HelmRepository`, `Bucket`, `Kustomization`, `HelmRelease`, `Alert` |
17+
| **ArgoCD** | `Application`, `ApplicationSet`, `AppProject` |
18+
19+
## Fleet view
20+
21+
Open the **GitOps** tab in the sidebar. Argo + Flux rows mix in the same table or tile view with resolved source URLs (`github.com/owner/repo`) for both ecosystems — not the CRD-internal source name.
22+
23+
- **Filters**: Sync, Health, Project, Namespace, Labels, Automation (auto-sync / manual / suspended), Lifecycle (active / terminating)
24+
- **Modes**: Applications / Sources / Projects / Alerts
25+
- **Default sort**: smart-tiered by urgency — Failed > Terminating > Degraded > Missing > OutOfSync > Suspended > Progressing > Synced
26+
27+
## Per-app detail page
28+
29+
Click any row to open a detail page with three top-level tabs.
30+
31+
<p align="center">
32+
<img src="screenshots/gitops-detail-drift.png" alt="GitOps detail page with stuck-drift-loop diagnosis" width="800">
33+
<br><em>Detail page — the diagnosis pipeline names the cause; field-level drift renders inline</em>
34+
</p>
35+
36+
### Topology tab
37+
38+
Graph or table sub-modes. The graph shows the application root and every managed resource, with ownership edges (Service → Deployment → ReplicaSet → Pod chains). Filter by kind, sync, health, role, namespace, or search. Group large pod sets to keep the graph scannable; click a group to expand.
39+
40+
GitOps CRs that are themselves managed (a child Application from an app-of-apps parent, a Kustomization referenced by another Kustomization, ApplicationSet children) render with a small `Argo` or `Flux` tool badge and a `` chevron — click to open that CR's own GitOps detail page. The child page's breadcrumb shows the lineage `GitOps / parent-ns/parent / child-ns/child`; the parent segment is clickable to navigate back.
41+
42+
### Changes tab
43+
44+
Per managed resource, in one row:
45+
46+
- **Sync status** chip (`Synced`, `OutOfSync`, etc.) with the health chip beside it
47+
- **Field-level drift** computed inline from each resource's `kubectl.kubernetes.io/last-applied-configuration` annotation vs live cluster state. You see actual `spec.X removed / spec.Y added` entries — no `argocd app diff` round-trip needed. Live objects are pulled via a dedicated direct-GET path so the dynamic informer cache doesn't have to retain the annotation cluster-wide
48+
- **Recent events** (5 most recent, namespace-RBAC-filtered) so `ImagePullBackOff`, `FailedScheduling`, webhook denials, PVC pending, etc. show up next to the resource that caused them
49+
- **Open** button — drops into the standard resource drawer
50+
51+
OutOfSync alerts in the Issues band at the top of the page are clickable — they jump to the affected row and highlight it for ~4 seconds.
52+
53+
### Activity tab
54+
55+
Operation history with deploy timestamps, the git revision deployed, who initiated the sync (human user or automation), an outcome chip per row, and per-revision **Rollback** buttons for Argo. In-progress operations pin to the top of the timeline.
56+
57+
## Diagnosis pipeline
58+
59+
The Issues band at the top of the detail page surfaces six classes of problems:
60+
61+
- **Operation failures** (Argo) — the parser recognizes 11 patterns (annotation-too-large, label-too-long, hook failure, admission webhook denial, RBAC, conflict, immutable field, schema migration, connectivity, etc.) and rewrites each into a plain-English cause
62+
- **Stuck-drift loop** — when sync succeeded but the app is *still* OutOfSync with auto-sync on and a recent reconcile, something is mutating resources after each apply. The Issue calls out likely culprits (mutating webhook, sibling controller, schema migration)
63+
- **Manual drift without auto-sync** — drift exists but auto-sync is disabled. The Issue tells you "nothing will reconcile until you click Sync" so you stop waiting
64+
- **Argo Application conditions**`ComparisonError` (verify repo creds), `OrphanedResourceWarning`, `InvalidSpecError`, etc. extracted into typed-severity Issues
65+
- **Per-resource health** — Degraded / Missing children get a critical Issue each, deduped against any operation failure that already named the same resource (no triplicate rendering)
66+
- **Pending deletion** (lifecycle) — see [Lifecycle awareness](#lifecycle-awareness) below
67+
68+
**Structured remediation** — when the diagnosis pipeline recognizes a fixable failure (e.g. Argo operation error "namespace X not found"), the Issue carries a primary-blue action button that performs the fix in one click. Duplicate per-resource Missing issues + SyncError condition rows are then suppressed so the user sees one clear "create the namespace and retry" path instead of three.
69+
70+
While an operation is running, the page polls every 2s; otherwise on-demand.
71+
72+
## Lifecycle awareness
73+
74+
When a GitOps resource is being deleted (`metadata.deletionTimestamp` set), its Sync and Health values are leftovers from the last reconcile *before* deletion was triggered. Showing them as if they're current produces contradictory state ("Syncing · Progressing · Terminating") that misleads operators into trying to fix routine sync problems on a resource the cluster is actively tearing down.
75+
76+
Radar treats Terminating as a distinct lifecycle phase that dominates other status:
77+
78+
- **Detail header**: orange `[Terminating]` chip replaces Sync/Health badges. Source / Revision / Last reconcile / Sync mode metadata swaps to `Pending deletion · Finalizers`, with the original fields behind a "Show pre-deletion metadata" toggle
79+
- **Action buttons**: Sync, Reconcile, Suspend / Resume, Rollback, Sync-with-source disable with a tooltip explaining why. Refresh and Terminate stay enabled — they're read-only / cleanup-only verbs
80+
- **Lifecycle banner**: a dedicated orange banner above the Issues band; pre-deletion failures collapse behind a `Pre-deletion issues (N)` disclosure
81+
- **Severity ramp**: info <5min, warning 5-30min, alert >30min. Past 30min the Issue's Cause line names the controller responsible for the finalizer and reports its pod state ("helm-controller is not running in flux-system")
82+
- **Fleet view**: `` in Sync/Health columns, orange row stripe, `[TERMINATING]` chip in the leftmost slot, `Pending Nago` instead of "Last Sync"
83+
- **Topology**: orange left-stripe on the root + children; stale sync/health chips suppressed
84+
- **Cluster Audit**: `stuckTerminating` check across all typed K8s resources with the same warning/alert thresholds
85+
- **Mutating ops** (`Sync`, `Reconcile`, `Rollback`, `SetAutoSync`, `SyncWithSource`) return `ErrResourceTerminating` (HTTP 409) on zombies, including over MCP
86+
87+
## Operations
88+
89+
### Argo CD
90+
91+
| Operation | What it does |
92+
|---|---|
93+
| **Sync…** | Opens a dialog with prune / dry-run / apply-only / force / replace / server-side apply / sync-options. Force-only routes via `syncStrategy.hook.force` so PreSync / PostSync hooks still run; ApplyOnly uses `syncStrategy.apply` |
94+
| **Refresh** | Re-fetches the source repo |
95+
| **Hard refresh** | Sets `RefreshType=hard` to bypass repo-cache |
96+
| **Terminate** | Cancels an in-flight operation |
97+
| **Suspend / Enable auto-sync** | Toggles automated sync; remembers the prior `prune` / `selfHeal` settings on suspend so resume restores them |
98+
| **Rollback** | Pick a prior history entry by ID. Force / DryRun flags honored |
99+
| **Selective sync** | From the Topology tab, mark individual resources and sync only those |
100+
101+
### Flux
102+
103+
| Operation | What it does |
104+
|---|---|
105+
| **Reconcile** | Annotates `reconcile.fluxcd.io/requestedAt` to trigger a single reconcile |
106+
| **Sync with source** (Kustomization / HelmRelease) | Reconciles the source first, then the resource itself |
107+
| **Suspend / Resume** | Toggles `spec.suspend` |
108+
109+
### Keyboard shortcuts (per-app detail page)
110+
111+
| Key | Action |
112+
|---|---|
113+
| `s` | Sync (opens the options dialog for Argo) / Reconcile (Flux) |
114+
| `r` | Refresh (Argo) |
115+
| `Shift+R` | Hard refresh (Argo) |
116+
| `t` | Terminate running sync (Argo, only when an op is in flight) |
117+
118+
## Cross-linking from the rest of Radar
119+
120+
The GitOps tab isn't the only place Argo/Flux ownership matters. Surfaces across Radar know about GitOps and route into the right detail page when they should:
121+
122+
- **K8s resource drawer** — every resource that carries Argo's tracking-id annotation or Flux's owner labels gets a `Managed by <app>` chip in the drawer header. Click → owning Application/Kustomization/HelmRelease detail page. The generic `app.kubernetes.io/instance` label is intentionally *not* used as a signal — it's stamped by virtually every Helm chart and would false-positive plain Helm installs
123+
- **Topology** — clicking an Argo / Flux CR node opens its GitOps detail page directly, not the generic drawer
124+
- **Timeline** — lane labels for Argo / Flux CRs route to detail
125+
- **Helm view** — releases installed by Flux's helm-controller (detected via a HelmRelease CR lookup keyed by `<storageNamespace>/<releaseName>`, since Flux's labels live on the *managed* resources, not the release Secret) carry a `Flux` badge in the list and an amber `Managed by Flux · ns/name` link in the drawer, warning that `helm upgrade` would be reverted at the next reconcile
126+
- **Flux source CR drawers**`GitRepository`, `HelmRepository`, `OCIRepository`, `Bucket` drawers carry a `Consumed by` panel listing every Kustomization + HelmRelease whose `spec.sourceRef` points at the source. Answers "if I edit this, what gets affected on the next reconcile?" without guessing
127+
128+
## MCP integration
129+
130+
`manage_gitops` MCP tool exposes the same actions to AI assistants with per-action input validation:
131+
132+
- Argo: `sync`, `suspend`, `resume`
133+
- Flux: `reconcile`, `suspend`, `resume`
134+
135+
`get_resource` Summary carries lifecycle signal (`terminating`, `finalizers`) so AI assistants can distinguish a zombie from a live resource and won't suggest `Sync` on something pending deletion.
136+
137+
See [MCP server](mcp.md) for the full tool list and security model.
138+
139+
## Demo cluster
140+
141+
`make gitops-demo` bootstraps a `kind` cluster pre-loaded with Argo CD + Flux + a curated set of fixtures covering every UI state the GitOps tab needs to render. Pinned to Argo CD `v2.13.2` + Flux `v2.4.0`.
142+
143+
```bash
144+
make gitops-demo # bootstrap + apply fixtures
145+
make gitops-demo-drift # induce stable drift on guestbook-drift
146+
make gitops-demo-down # teardown
147+
make gitops-demo-status # inventory what's installed
148+
```
149+
150+
Fixtures cover healthy + drifted + broken-sync + broken-path + manual-sync + suspended + rollback history + ApplicationSet → 3 children + Flux Kustomization with `dependsOn` chain + HelmRelease (managed-resources tree) + Flux zombie (Terminating lifecycle) + broken Kustomization (missing namespace) + broken HelmRelease (no-such-version, retry counter). See `scripts/gitops-demo/README.md` for the full matrix.
151+
152+
## RBAC
153+
154+
Reading status only needs the default ClusterRole's read access on `argoproj.io` / `*.toolkit.fluxcd.io` (enabled by default in the Helm chart's `rbac.crdGroups.argo` / `.flux`).
155+
156+
Triggering Sync / Reconcile / Suspend / Rollback needs `patch` on the parent CRDs. The chart enables the right verbs when `rbac.helm: true` is set, or you can scope it more tightly via `rbac.additionalRules`.
157+
158+
The lifecycle controller-health probe (Home dashboard `GitOps Controllers` card, finalizer-owner attribution in lifecycle Issues) reads pods in `argocd` and `flux-system`. Operators with no access there still get the chip and the basic Issue; the controller-status enrichment is omitted gracefully.
159+
160+
## Single-cluster scope
161+
162+
Radar shows GitOps connections only when the controller and managed resources live in the same cluster. ArgoCD's hub-spoke pattern (controller in one cluster, workloads in another) means Application → resource edges won't render when you're connected to the ArgoCD hub. Flux typically deploys to its own cluster, so connections usually work.
163+
164+
## See also
165+
166+
- [MCP server](mcp.md) — how AI assistants drive GitOps operations and read lifecycle signal
167+
- [Integrations & CRDs](integrations.md) — full CRD support matrix for Argo CD, Flux, and everything else
168+
- [Configuration](configuration.md) — cluster connection, multi-context kubeconfig handling

docs/integrations.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ Radar has first-class renderers for **AWS (CAPA)**, **GCP (CAPG)**, and **Azure
480480

481481
## GitOps
482482

483-
See the main [README](../README.md#gitops) for GitOps integration details.
483+
See the main [README](../README.md#gitops) for the user-facing overview. This section covers integration coverage and capabilities.
484484

485485
### FluxCD
486486

@@ -493,6 +493,10 @@ See the main [README](../README.md#gitops) for GitOps integration details.
493493
| HelmRelease | `helm.toolkit.fluxcd.io/v2` | Yes | Yes | Yes |
494494
| Alert | `notification.toolkit.fluxcd.io/v1beta3` || Yes ||
495495

496+
**Workflow operations**: Reconcile, Reconcile-with-source (Kustomization/HelmRelease), Suspend/Resume.
497+
498+
**Diagnosis**: Conditions extracted to issues (Ready=False, Stalled=True, Reconciling=True). Per-resource diff and recent events not yet available for Flux (HelmRelease-installed resources don't carry `last-applied-configuration`; tracked in [#601](https://github.com/skyhook-io/radar/issues/601)).
499+
496500
### ArgoCD
497501

498502
| CRD | Group | Topology | Detail View | AI Summary |
@@ -501,6 +505,20 @@ See the main [README](../README.md#gitops) for GitOps integration details.
501505
| ApplicationSet | `argoproj.io/v1alpha1` || Generic ||
502506
| AppProject | `argoproj.io/v1alpha1` || Generic ||
503507

508+
**Workflow operations**: Sync (with options dialog: prune, dry-run, apply-only, force, replace, server-side apply, sync-options), Refresh, Hard refresh, Terminate, Suspend/Resume auto-sync, Rollback to historical revision, Selective sync of marked resources.
509+
510+
**Diagnosis**:
511+
- **Per-resource field diff** computed from each resource's `kubectl.kubernetes.io/last-applied-configuration` annotation vs live spec — works for any Argo client-side-applied resource without calling the Argo API
512+
- **Recent events** surfaced inline per managed resource (5 most recent, namespace-RBAC-filtered)
513+
- **Stuck-drift-loop detector** — flags `sync=OutOfSync ∧ opPhase=Succeeded ∧ auto-sync on ∧ reconciledAt<30min` with the likely cause (mutating webhook, sibling controller, schema migration)
514+
- **Manual-drift detector** — calls out OutOfSync apps with auto-sync disabled
515+
- **Argo Application conditions** extracted to issues (ComparisonError, OrphanedResourceWarning, etc.) with type-aware severity and per-condition action text
516+
- **Operation-failure parser** recognizes 11 patterns: annotation-too-large, label-too-long, hook failure, admission webhook denial, RBAC, conflict, immutable field, schema migration, connectivity, etc.
517+
518+
**Limitations**:
519+
- SSA-applied resources (`ServerSideApply=true` sync-option) lack the `last-applied-configuration` annotation; per-resource diff is unavailable for those rows. SSA fallback via `metadata.managedFields` and Argo API integration for canonical Git-rendered diffs are tracked in [#601](https://github.com/skyhook-io/radar/issues/601).
520+
- Single-cluster only: Application↔resource edges only render when Radar is connected to the cluster where the managed resources live (not necessarily the cluster running the Argo controller).
521+
504522
---
505523

506524
## Argo Rollouts

0 commit comments

Comments
 (0)