package inspector import ( "time" ) // Severity levels for check results. type Severity int const ( Low Severity = iota Medium High Critical ) func (s Severity) String() string { switch s { case Low: return "LOW" case Medium: return "MEDIUM" case High: return "HIGH" case Critical: return "CRITICAL" default: return "UNKNOWN" } } // Status represents the outcome of a check. type Status string const ( StatusPass Status = "pass" StatusFail Status = "fail" StatusWarn Status = "warn" StatusSkip Status = "skip" ) // CheckResult holds the outcome of a single health check. type CheckResult struct { ID string `json:"id"` // e.g. "rqlite.leader_exists" Name string `json:"name"` // "Cluster has exactly one leader" Subsystem string `json:"subsystem"` // "rqlite" Severity Severity `json:"severity"` Status Status `json:"status"` Message string `json:"message"` // human-readable detail Node string `json:"node,omitempty"` // which node (empty for cluster-wide) } // Results holds all check outcomes. type Results struct { Checks []CheckResult `json:"checks"` Duration time.Duration `json:"duration"` } // Summary returns counts by status. func (r *Results) Summary() (passed, failed, warned, skipped int) { for _, c := range r.Checks { switch c.Status { case StatusPass: passed++ case StatusFail: failed++ case StatusWarn: warned++ case StatusSkip: skipped++ } } return } // Failures returns only failed checks. func (r *Results) Failures() []CheckResult { var out []CheckResult for _, c := range r.Checks { if c.Status == StatusFail { out = append(out, c) } } return out } // FailuresAndWarnings returns failed and warning checks. func (r *Results) FailuresAndWarnings() []CheckResult { var out []CheckResult for _, c := range r.Checks { if c.Status == StatusFail || c.Status == StatusWarn { out = append(out, c) } } return out } // CheckFunc is the signature for a subsystem check function. type CheckFunc func(data *ClusterData) []CheckResult // SubsystemCheckers maps subsystem names to their check functions. // Populated by checks/ package init or by explicit registration. var SubsystemCheckers = map[string]CheckFunc{} // RegisterChecker registers a check function for a subsystem. func RegisterChecker(subsystem string, fn CheckFunc) { SubsystemCheckers[subsystem] = fn } // RunChecks executes checks for the requested subsystems against collected data. func RunChecks(data *ClusterData, subsystems []string) *Results { start := time.Now() results := &Results{} shouldCheck := func(name string) bool { if len(subsystems) == 0 { return true } for _, s := range subsystems { if s == name || s == "all" { return true } // Alias: "wg" matches "wireguard" if s == "wg" && name == "wireguard" { return true } } return false } for name, fn := range SubsystemCheckers { if shouldCheck(name) { checks := fn(data) results.Checks = append(results.Checks, checks...) } } results.Duration = time.Since(start) return results } // Pass creates a passing check result. func Pass(id, name, subsystem, node, msg string, sev Severity) CheckResult { return CheckResult{ ID: id, Name: name, Subsystem: subsystem, Severity: sev, Status: StatusPass, Message: msg, Node: node, } } // Fail creates a failing check result. func Fail(id, name, subsystem, node, msg string, sev Severity) CheckResult { return CheckResult{ ID: id, Name: name, Subsystem: subsystem, Severity: sev, Status: StatusFail, Message: msg, Node: node, } } // Warn creates a warning check result. func Warn(id, name, subsystem, node, msg string, sev Severity) CheckResult { return CheckResult{ ID: id, Name: name, Subsystem: subsystem, Severity: sev, Status: StatusWarn, Message: msg, Node: node, } } // Skip creates a skipped check result. func Skip(id, name, subsystem, node, msg string, sev Severity) CheckResult { return CheckResult{ ID: id, Name: name, Subsystem: subsystem, Severity: sev, Status: StatusSkip, Message: msg, Node: node, } }