87 lines
3.3 KiB
Go
87 lines
3.3 KiB
Go
package cluster
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type DriftIssue struct {
|
|
Level string `json:"level"`
|
|
Kind string `json:"kind"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type DriftReport struct {
|
|
Cluster string `json:"cluster"`
|
|
Generated time.Time `json:"generated_at"`
|
|
Issues []DriftIssue `json:"issues,omitempty"`
|
|
}
|
|
|
|
func (s *Service) DetectDrift(ctx context.Context, selector string) (DriftReport, error) {
|
|
c, err := s.Get(selector)
|
|
if err != nil {
|
|
return DriftReport{}, err
|
|
}
|
|
rep := DriftReport{Cluster: c.Name, Generated: s.now().UTC()}
|
|
expected := strings.TrimSpace(s.ExpectedAgentVersion())
|
|
dctrl := normalizeDriftControl(c.Drift)
|
|
|
|
if !c.Agent.Installed {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "warn", Kind: "agent", Message: "agent is not installed"})
|
|
return rep, nil
|
|
}
|
|
|
|
pingCtx, cancel := context.WithTimeout(ctx, 2500*time.Millisecond)
|
|
ping, pingErr := s.PingAgent(pingCtx, c.ID)
|
|
cancel()
|
|
if pingErr != nil || !ping.Reachable || ping.StatusCode >= 400 {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "crit", Kind: "agent", Message: "agent is unreachable"})
|
|
} else {
|
|
nodeVersion := strings.TrimSpace(ping.Version)
|
|
if nodeVersion == "" {
|
|
nodeVersion = strings.TrimSpace(c.Agent.Version)
|
|
}
|
|
if expected != "" && nodeVersion != "" && nodeVersion != expected {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "warn", Kind: "agent_version", Message: fmt.Sprintf("agent version mismatch: node=%s local=%s", nodeVersion, expected)})
|
|
}
|
|
if nodeVersion != "" {
|
|
if ok, latest, known := s.CompareAgentVersion(nodeVersion); known && !ok {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "warn", Kind: "agent_outdated", Message: fmt.Sprintf("node version %s is older than latest known %s", nodeVersion, latest)})
|
|
}
|
|
if dctrl.Baseline.Enabled && strings.TrimSpace(dctrl.Baseline.AgentVersion) != "" && !strings.EqualFold(strings.TrimSpace(dctrl.Baseline.AgentVersion), nodeVersion) {
|
|
rep.Issues = append(rep.Issues, DriftIssue{
|
|
Level: "warn",
|
|
Kind: "baseline_agent_version",
|
|
Message: fmt.Sprintf("baseline agent version mismatch: baseline=%s live=%s", dctrl.Baseline.AgentVersion, nodeVersion),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
softCtx, softCancel := context.WithTimeout(ctx, 7*time.Second)
|
|
fresh, softErr := s.probeSoftware(softCtx, c, "", "")
|
|
softCancel()
|
|
if softErr != nil {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "warn", Kind: "software_probe", Message: softErr.Error()})
|
|
} else {
|
|
oldSet := strings.TrimSpace(c.Software.Summary())
|
|
newSet := strings.TrimSpace(fresh.Summary())
|
|
if oldSet != "" && oldSet != "-" && newSet != oldSet {
|
|
rep.Issues = append(rep.Issues, DriftIssue{Level: "warn", Kind: "software", Message: fmt.Sprintf("software support changed: stored=%s live=%s", oldSet, newSet)})
|
|
}
|
|
if dctrl.Baseline.Enabled && strings.TrimSpace(dctrl.Baseline.Software) != "" && !strings.EqualFold(strings.TrimSpace(dctrl.Baseline.Software), newSet) {
|
|
rep.Issues = append(rep.Issues, DriftIssue{
|
|
Level: "warn",
|
|
Kind: "baseline_software",
|
|
Message: fmt.Sprintf("baseline software mismatch: baseline=%s live=%s", dctrl.Baseline.Software, newSet),
|
|
})
|
|
}
|
|
}
|
|
|
|
sort.Slice(rep.Issues, func(i, j int) bool { return rep.Issues[i].Kind < rep.Issues[j].Kind })
|
|
return rep, nil
|
|
}
|