checker-email-keys/checker/rule.go

109 lines
3 KiB
Go

package checker
import (
"context"
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
)
// issue is a rule-internal description of a failed test. Rules return a
// slice of issues from their check func; Evaluate converts them to
// sdk.CheckState.
type issue struct {
Severity sdk.Status // StatusInfo / StatusWarn / StatusCrit
Message string
Hint string // remediation hint; surfaced as Meta["hint"]
Subject string // optional; overrides default data.QueriedOwner
}
// ruleFunc consumes the facts + runtime options and returns zero or more
// issues. No issues means the test passed.
type ruleFunc func(d *EmailKeyData, opts sdk.CheckerOptions) []issue
// rule is a data-driven CheckRule. All per-test rules share this type;
// only name / description / applicable kinds / options / check differ.
type rule struct {
name string
description string
okMessage string // message for StatusOK returns
kinds []string // applicable kinds; empty = both
options sdk.CheckerOptionsDocumentation // per-rule options
check ruleFunc
}
func (r *rule) Name() string { return r.name }
func (r *rule) Description() string { return r.description }
func (r *rule) Options() sdk.CheckerOptionsDocumentation { return r.options }
func (r *rule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
var data EmailKeyData
if err := obs.Get(ctx, ObservationKey, &data); err != nil {
return []sdk.CheckState{{
Status: sdk.StatusError,
Message: fmt.Sprintf("Failed to read observation %q: %v", ObservationKey, err),
Code: "openpgpkey_observation_error",
}}
}
if len(r.kinds) > 0 && !containsString(r.kinds, data.Kind) {
return []sdk.CheckState{{
Status: sdk.StatusUnknown,
Message: fmt.Sprintf("Not applicable for %s records.", data.Kind),
Code: r.name,
Subject: data.QueriedOwner,
}}
}
issues := r.check(&data, opts)
if len(issues) == 0 {
msg := r.okMessage
if msg == "" {
msg = "Check passed."
}
return []sdk.CheckState{{
Status: sdk.StatusOK,
Message: msg,
Code: r.name,
Subject: data.QueriedOwner,
}}
}
states := make([]sdk.CheckState, 0, len(issues))
for _, iss := range issues {
subject := iss.Subject
if subject == "" {
subject = data.QueriedOwner
}
var meta map[string]any
if iss.Hint != "" {
meta = map[string]any{"hint": iss.Hint}
}
states = append(states, sdk.CheckState{
Status: iss.Severity,
Message: iss.Message,
Code: r.name,
Subject: subject,
Meta: meta,
})
}
return states
}
// Rules returns the full set of per-test rules for this checker.
func Rules() []sdk.CheckRule {
out := make([]sdk.CheckRule, len(allRules))
for i := range allRules {
out[i] = allRules[i]
}
return out
}
func containsString(hay []string, needle string) bool {
for _, v := range hay {
if v == needle {
return true
}
}
return false
}