Repository navigation
Per-resource health with provenance when Argo CD doesn't persist it #1743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
5893524
feat(gitops): per-resource health with provenance when Argo CD doesn'…
nadaverell c79daa9
fix(gitops): remote apps read nothing local; engine finding replaces …
nadaverell a3cac40
fix(gitops): remote apps skip local drift/event enrichment; demo inst…
nadaverell aa368e2
fix(gitops): mark only declared nodes as Radar's; no appTree notice o…
nadaverell 3a4ff59
feat(gitops): remote-destination notice opens the Radar Cloud dialog
nadaverell b6a1164
copy(gitops): name Argo CD 3 in the health notice; clearer marker and…
nadaverell 3cfa966
copy(gitops): notice names the marked rows; cloud hint promises the v…
nadaverell 5bb75d6
fix(gitops): the events lead yields to a Radar finding on the same app
nadaverell 3a268c6
fix(gitops): findings of any tier own the row; topology reads are war…
nadaverell 74a8079
fix(gitops): the degraded count doesn't contradict a Healthy app either
nadaverell 9ebfe70
perf(gitops): memoize the issues-engine composition across the detail…
nadaverell a28e1e6
copy(gitops): say so when Radar found nothing to mark, and name the w…
nadaverell c383e79
fix(gitops): the notice reads the same rows the Found-by-Radar marker…
nadaverell 9af7366
gitops: point the health notice's Why link at the docs section (radar…
nadaverell 454fd4c
chore: keep the plan doc out of the repo
nadaverell 09ae0e0
CLAUDE.md: one-line rule for per-resource health reads
nadaverell 7c58e43
copy(gitops): the no-findings notice only on an app Argo calls Degraded
nadaverell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package server | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| gitopsinsights "github.com/skyhook-io/radar/pkg/gitops/insights" | ||
| gitopstree "github.com/skyhook-io/radar/pkg/gitops/tree" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| ) | ||
|
|
||
| func overlayApp(health string) *unstructured.Unstructured { | ||
| return &unstructured.Unstructured{Object: map[string]any{ | ||
| "apiVersion": "argoproj.io/v1alpha1", | ||
| "kind": "Application", | ||
| "metadata": map[string]any{"name": "app", "namespace": "argocd"}, | ||
| "status": map[string]any{"health": map[string]any{"status": health}, "resourceHealthSource": "appTree"}, | ||
| }} | ||
| } | ||
|
|
||
| func overlayTree(mode gitopstree.HealthMode, remote bool) *gitopstree.ResourceTree { | ||
| return &gitopstree.ResourceTree{ | ||
| HealthMode: mode, | ||
| RemoteDestination: remote, | ||
| Nodes: []gitopstree.Node{ | ||
| {Role: gitopstree.RoleRoot, Ref: gitopstree.ResourceRef{Group: "argoproj.io", Kind: "Application", Namespace: "argocd", Name: "app"}, Health: "Degraded"}, | ||
| {Role: gitopstree.RoleDeclared, Ref: gitopstree.ResourceRef{Group: "external-secrets.io", Kind: "ClusterSecretStore", Name: "platform"}}, | ||
| {Role: gitopstree.RoleDeclared, Ref: gitopstree.ResourceRef{Group: "apps", Kind: "Deployment", Namespace: "prod", Name: "web"}, Health: "Progressing", HealthSource: gitopstree.HealthSourceRadar, TopologyStatus: "degraded"}, | ||
| {Role: gitopstree.RoleDeclared, Ref: gitopstree.ResourceRef{Group: "apps", Kind: "Deployment", Namespace: "prod", Name: "api"}, Health: "Healthy", HealthSource: gitopstree.HealthSourceController, TopologyStatus: "healthy"}, | ||
| {Role: gitopstree.RoleDeclared, Ref: gitopstree.ResourceRef{Kind: "ConfigMap", Namespace: "prod", Name: "vars"}}, | ||
| {Role: gitopstree.RoleGenerated, Ref: gitopstree.ResourceRef{Kind: "Pod", Namespace: "prod", Name: "web-1"}}, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func fakeProblems(calls *[]string) func(group, kind, namespace, name string) []gitopsinsights.ResourceProblem { | ||
| return func(group, kind, namespace, name string) []gitopsinsights.ResourceProblem { | ||
| *calls = append(*calls, kind+"/"+name) | ||
| switch name { | ||
| case "platform": | ||
| return []gitopsinsights.ResourceProblem{ | ||
| {Reason: "Ready: InvalidProviderConfig", Message: "no route to host", Category: "condition_false", Severity: "warning"}, | ||
| } | ||
| case "web", "api": | ||
| return []gitopsinsights.ResourceProblem{{Reason: "CrashLoopBackOff", Message: "back-off", Severity: "critical"}} | ||
| } | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayRadarHealth_FillsOnlyEmptyDeclaredNodes(t *testing.T) { | ||
| tree := overlayTree(gitopstree.HealthModeAppTree, false) | ||
| var calls []string | ||
| overlayRadarHealth(tree, overlayApp("Degraded"), fakeProblems(&calls)) | ||
|
|
||
| css := tree.Nodes[1] | ||
| if css.Health != "Degraded" || css.HealthSource != gitopstree.HealthSourceRadar || css.HealthReason != "Ready: InvalidProviderConfig" || css.HealthMessage != "no route to host" || css.HealthSeverity != "warning" || css.TopologyStatus != "unhealthy" { | ||
| t.Errorf("ClusterSecretStore should carry the engine's finding as Radar-sourced Degraded, got %+v", css) | ||
| } | ||
| if dep := tree.Nodes[2]; dep.Health != "Degraded" || dep.HealthReason != "CrashLoopBackOff" || dep.HealthSeverity != "critical" || dep.TopologyStatus != "unhealthy" { | ||
| t.Errorf("Radar's topology read (Progressing) must give way to the engine's classified finding, got %+v", dep) | ||
| } | ||
| if api := tree.Nodes[3]; api.Health != "Healthy" || api.HealthSource != gitopstree.HealthSourceController || api.HealthReason != "" { | ||
| t.Errorf("controller-sourced health is final; the engine must not overwrite it, got %+v", api) | ||
| } | ||
| if cm := tree.Nodes[4]; cm.Health != "" || cm.HealthSource != "" { | ||
| t.Errorf("a node the engine has nothing on must stay without health, got %+v", cm) | ||
| } | ||
| for _, c := range calls { | ||
| if c == "Pod/web-1" || c == "Deployment/api" { | ||
| t.Errorf("generated nodes and controller-assessed nodes must not be looked up; saw %q", c) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayRadarHealth_SkipsInlineModeRemoteDestinationAndHealthyApp(t *testing.T) { | ||
| cases := []struct { | ||
| name string | ||
| tree *gitopstree.ResourceTree | ||
| app *unstructured.Unstructured | ||
| }{ | ||
| {"inline mode", overlayTree(gitopstree.HealthModeInline, false), overlayApp("Degraded")}, | ||
| {"remote destination", overlayTree(gitopstree.HealthModeAppTree, true), overlayApp("Degraded")}, | ||
| {"healthy app", overlayTree(gitopstree.HealthModeAppTree, false), overlayApp("Healthy")}, | ||
| } | ||
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| var calls []string | ||
| overlayRadarHealth(tc.tree, tc.app, fakeProblems(&calls)) | ||
| if len(calls) != 0 { | ||
| t.Errorf("engine must not be consulted, got calls %v", calls) | ||
| } | ||
| if tc.tree.Nodes[1].Health != "" { | ||
| t.Errorf("no health may be overlaid, got %+v", tc.tree.Nodes[1]) | ||
| } | ||
| }) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.