149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
// validateXMPPOptions is the shared options validator for both the provider
|
|
// and the aggregate rule.
|
|
func validateXMPPOptions(opts sdk.CheckerOptions) error {
|
|
if v, ok := opts["mode"]; ok {
|
|
if s, ok := v.(string); ok && s != "" && !slices.Contains(validModes, s) {
|
|
return fmt.Errorf(`mode must be "c2s", "s2s", or "both"`)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateOptions implements sdk.OptionsValidator on the provider.
|
|
func (p *xmppProvider) ValidateOptions(opts sdk.CheckerOptions) error {
|
|
return validateXMPPOptions(opts)
|
|
}
|
|
|
|
// xmppRule is a minimal back-compat aggregate rule. Newer deployments should
|
|
// prefer the split per-concern rules exposed by Rules(); this one is kept so
|
|
// existing tests that compose a single-status output keep working.
|
|
type xmppRule struct{}
|
|
|
|
func (r *xmppRule) Name() string { return "xmpp_server" }
|
|
func (r *xmppRule) Description() string {
|
|
return "Aggregate XMPP posture (prefer the per-concern rules)."
|
|
}
|
|
|
|
func (r *xmppRule) ValidateOptions(opts sdk.CheckerOptions) error {
|
|
return validateXMPPOptions(opts)
|
|
}
|
|
|
|
func (r *xmppRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
data, errSt := loadXMPPData(ctx, obs)
|
|
if errSt != nil {
|
|
return []sdk.CheckState{*errSt}
|
|
}
|
|
wantC2S, wantS2S := wantsFromOpts(opts)
|
|
issues := deriveIssues(data, wantC2S, wantS2S)
|
|
|
|
// Fold related TLS observations into the aggregate so cert/chain
|
|
// problems surface on the XMPP service page.
|
|
related, _ := obs.GetRelated(ctx, TLSRelatedKey)
|
|
issues = append(issues, tlsIssuesFromRelated(related)...)
|
|
|
|
worst := sdk.StatusOK
|
|
var critMsgs, warnMsgs []string
|
|
var firstCritCode, firstWarnCode string
|
|
for _, is := range issues {
|
|
switch is.Severity {
|
|
case SeverityCrit:
|
|
if worst < sdk.StatusCrit {
|
|
worst = sdk.StatusCrit
|
|
}
|
|
if firstCritCode == "" {
|
|
firstCritCode = is.Code
|
|
}
|
|
critMsgs = append(critMsgs, is.Message)
|
|
case SeverityWarn:
|
|
if worst < sdk.StatusWarn {
|
|
worst = sdk.StatusWarn
|
|
}
|
|
if firstWarnCode == "" {
|
|
firstWarnCode = is.Code
|
|
}
|
|
warnMsgs = append(warnMsgs, is.Message)
|
|
}
|
|
}
|
|
|
|
if (wantC2S && !data.Coverage.WorkingC2S) || (wantS2S && !data.Coverage.WorkingS2S) {
|
|
if worst < sdk.StatusCrit {
|
|
worst = sdk.StatusCrit
|
|
}
|
|
var missing []string
|
|
if wantC2S && !data.Coverage.WorkingC2S {
|
|
missing = append(missing, "c2s")
|
|
}
|
|
if wantS2S && !data.Coverage.WorkingS2S {
|
|
missing = append(missing, "s2s")
|
|
}
|
|
critMsgs = append(critMsgs, "no working "+joinModes(missing)+" endpoint")
|
|
if firstCritCode == "" {
|
|
firstCritCode = CodeAllEndpointsDown
|
|
}
|
|
}
|
|
|
|
meta := map[string]any{
|
|
"working_c2s": data.Coverage.WorkingC2S,
|
|
"working_s2s": data.Coverage.WorkingS2S,
|
|
"has_ipv4": data.Coverage.HasIPv4,
|
|
"has_ipv6": data.Coverage.HasIPv6,
|
|
"endpoints": len(data.Endpoints),
|
|
"issue_count": len(issues),
|
|
}
|
|
|
|
switch worst {
|
|
case sdk.StatusOK:
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusOK,
|
|
Message: fmt.Sprintf("XMPP operational (c2s=%v, s2s=%v, %d endpoints)", data.Coverage.WorkingC2S, data.Coverage.WorkingS2S, len(data.Endpoints)),
|
|
Code: "xmpp.ok",
|
|
Meta: meta,
|
|
}}
|
|
case sdk.StatusWarn:
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusWarn,
|
|
Message: "XMPP works with warnings: " + joinTop(warnMsgs, 2),
|
|
Code: firstWarnCode,
|
|
Meta: meta,
|
|
}}
|
|
default:
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusCrit,
|
|
Message: "XMPP broken: " + joinTop(critMsgs, 2),
|
|
Code: firstCritCode,
|
|
Meta: meta,
|
|
}}
|
|
}
|
|
}
|
|
|
|
func joinModes(ms []string) string {
|
|
switch len(ms) {
|
|
case 0:
|
|
return ""
|
|
case 1:
|
|
return ms[0]
|
|
default:
|
|
return ms[0] + "/" + ms[1]
|
|
}
|
|
}
|
|
|
|
func joinTop(msgs []string, n int) string {
|
|
if len(msgs) == 0 {
|
|
return ""
|
|
}
|
|
if len(msgs) <= n {
|
|
return strings.Join(msgs, "; ")
|
|
}
|
|
return strings.Join(msgs[:n], "; ") + fmt.Sprintf(" (+%d more)", len(msgs)-n)
|
|
}
|