internal/argocd/diagnose.go
Rama main · réplica de lectura del repositorio original.
package argocd
import (
"strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/sophotechlabs/spinoza/internal/api"
"github.com/sophotechlabs/spinoza/internal/faults"
"github.com/sophotechlabs/spinoza/internal/unstr"
)
const (
outOfSync = "OutOfSync"
succeededPhase = "Succeeded"
degraded = "Degraded"
missing = "Missing"
)
var conditionAdvice = map[string]string{
"ComparisonError": "check the repository credentials and the path",
"InvalidSpecError": "argo cd will not sync until the spec is fixed",
"OrphanedResourceWarning": "the destination namespace holds resources this application does not declare",
"SharedResourceWarning": "another application declares the same resource",
"RepeatedResourceWarning": "the same resource is declared twice in this application",
"ExcludedResourceWarning": "argo cd's settings exclude it, so it is never applied",
"SyncError": "the last sync did not finish",
"DeletionError": "argo cd could not delete something it had applied",
}
func issuesOfApp(app *unstructured.Unstructured) []api.GitopsIssue {
out := make([]api.GitopsIssue, 0, 4)
out = append(out, terminatingIssue(app)...)
out = append(out, conditionIssues(app)...)
out = append(out, operationIssue(app)...)
out = append(out, driftIssues(app)...)
out = append(out, healthIssues(app)...)
if len(out) == 0 {
return nil
}
return out
}
func terminatingIssue(app *unstructured.Unstructured) []api.GitopsIssue {
if app.GetDeletionTimestamp() == nil {
return nil
}
return []api.GitopsIssue{{
Severity: api.SeverityLow,
Title: "This application is being deleted",
Detail: heldBy(app.GetFinalizers()),
}}
}
func heldBy(finalizers []string) string {
if len(finalizers) == 0 {
return "no finalizers left"
}
return "held by " + strings.Join(finalizers, ", ")
}
func conditionIssues(app *unstructured.Unstructured) []api.GitopsIssue {
out := []api.GitopsIssue{}
for _, raw := range unstr.Slice(app, "status", "conditions") {
entry, ok := raw.(map[string]any)
if !ok {
continue
}
kind := unstr.At(entry, "type")
if kind == "" {
continue
}
out = append(out, api.GitopsIssue{
Severity: severityOf(kind),
Title: unstr.At(entry, "message"),
Detail: adviceFor(kind),
Subject: kind,
})
}
return out
}
func severityOf(kind string) string {
if strings.HasSuffix(kind, "Warning") {
return api.SeverityLow
}
if strings.HasSuffix(kind, "Error") {
return api.SeverityMedium
}
return api.SeverityLow
}
func adviceFor(kind string) string {
advice, ok := conditionAdvice[kind]
if !ok {
return ""
}
return advice
}
func operationIssue(app *unstructured.Unstructured) []api.GitopsIssue {
phase := unstr.String(app, "status", "operationState", "phase")
if phase == "" || phase == succeededPhase || phase == runningPhase {
return nil
}
message := unstr.String(app, "status", "operationState", "message")
return []api.GitopsIssue{{
Severity: api.SeverityHigh,
Title: "The last operation " + strings.ToLower(phase),
Detail: detailOf(message),
Subject: "operation",
}}
}
func detailOf(message string) string {
cause := faults.Cause(message)
if cause == "" {
return message
}
return cause + " — " + message
}
func driftIssues(app *unstructured.Unstructured) []api.GitopsIssue {
if unstr.String(app, "status", "sync", "status") != outOfSync {
return nil
}
if !AutoSyncing(app) {
return []api.GitopsIssue{{
Severity: api.SeverityLow,
Title: "Nothing will reconcile this",
Detail: "auto-sync is off; nothing changes until someone syncs",
Subject: "drift",
}}
}
if unstr.String(app, "status", "operationState", "phase") != succeededPhase {
return nil
}
return []api.GitopsIssue{{
Severity: api.SeverityLow,
Title: "Synced, and out of sync again",
Detail: "the last sync succeeded and auto-sync is on, so something rewrites these fields after every apply: " +
"a mutating webhook, a second controller, or a migration",
Subject: "drift",
}}
}
func healthIssues(app *unstructured.Unstructured) []api.GitopsIssue {
blamed := unstr.String(app, "status", "operationState", "message")
seen := map[string]bool{}
out := []api.GitopsIssue{}
for _, one := range resourcesOfApp(app) {
if one.Health != degraded && one.Health != missing {
continue
}
subject := one.Kind + "/" + one.Name
if seen[subject] {
continue
}
seen[subject] = true
if strings.Contains(blamed, one.Name) {
continue
}
out = append(out, api.GitopsIssue{
Severity: healthSeverity(one.Health),
Title: subject + " is " + strings.ToLower(one.Health),
Detail: one.Message,
Subject: subject,
})
}
return out
}
func healthSeverity(health string) string {
if health == degraded {
return api.SeverityHigh
}
return api.SeverityMedium
}