checker: let CheckRule.Evaluate return per-subject CheckStates
Rules that iterate over multiple elements (certificates, CAA records, nameservers, …) previously had to squash per-element results into a single concatenated message. Evaluate now returns []CheckState and CheckState carries an opaque Subject, so each element gets its own structured state. The server injects a StatusUnknown placeholder when a rule returns nothing, to avoid silently dropping the rule.
This commit is contained in:
parent
7567271536
commit
d847c71a50
3 changed files with 30 additions and 9 deletions
|
|
@ -291,11 +291,19 @@ func (s *Server) handleEvaluate(w http.ResponseWriter, r *http.Request) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state := rule.Evaluate(r.Context(), obs, req.Options)
|
ruleStates := rule.Evaluate(r.Context(), obs, req.Options)
|
||||||
if state.Code == "" {
|
if len(ruleStates) == 0 {
|
||||||
state.Code = rule.Name()
|
ruleStates = []CheckState{{
|
||||||
|
Status: StatusUnknown,
|
||||||
|
Message: fmt.Sprintf("rule %q returned no state", rule.Name()),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
for _, state := range ruleStates {
|
||||||
|
if state.Code == "" {
|
||||||
|
state.Code = rule.Name()
|
||||||
|
}
|
||||||
|
states = append(states, state)
|
||||||
}
|
}
|
||||||
states = append(states, state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, ExternalEvaluateResponse{States: states})
|
writeJSON(w, http.StatusOK, ExternalEvaluateResponse{States: states})
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ type dummyRule struct {
|
||||||
|
|
||||||
func (r *dummyRule) Name() string { return r.name }
|
func (r *dummyRule) Name() string { return r.name }
|
||||||
func (r *dummyRule) Description() string { return r.desc }
|
func (r *dummyRule) Description() string { return r.desc }
|
||||||
func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) CheckState {
|
func (r *dummyRule) Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) []CheckState {
|
||||||
return CheckState{Status: StatusOK, Message: r.name + " passed"}
|
return []CheckState{{Status: StatusOK, Message: r.name + " passed"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- helpers ---
|
// --- helpers ---
|
||||||
|
|
|
||||||
|
|
@ -182,11 +182,15 @@ func (s Status) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckState is the result of evaluating a single rule.
|
// CheckState is the result of evaluating a single rule on a single subject.
|
||||||
|
// Subject is opaque to the SDK: producers and consumers agree on its shape
|
||||||
|
// (a hostname, a record key, a serial, …). Leave Subject empty for rules
|
||||||
|
// that produce a single, global result.
|
||||||
type CheckState struct {
|
type CheckState struct {
|
||||||
Status Status `json:"status"`
|
Status Status `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Code string `json:"code,omitempty"`
|
Code string `json:"code,omitempty"`
|
||||||
|
Subject string `json:"subject,omitempty"`
|
||||||
Meta map[string]any `json:"meta,omitempty"`
|
Meta map[string]any `json:"meta,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,11 +226,20 @@ type CheckRuleInfo struct {
|
||||||
Options *CheckerOptionsDocumentation `json:"options,omitempty"`
|
Options *CheckerOptionsDocumentation `json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRule evaluates observations and produces a CheckState.
|
// CheckRule evaluates observations and produces one or more CheckStates.
|
||||||
|
//
|
||||||
|
// Evaluate returns a slice so a rule iterating over multiple elements can
|
||||||
|
// emit one state per subject (each carrying CheckState.Subject) without
|
||||||
|
// squashing them into a single concatenated message.
|
||||||
|
//
|
||||||
|
// Evaluate must not return a nil or empty slice: callers expect at least
|
||||||
|
// one state per rule. When a rule finds nothing to evaluate, return a
|
||||||
|
// single CheckState with an appropriate status (typically StatusInfo or
|
||||||
|
// StatusOK) describing that fact.
|
||||||
type CheckRule interface {
|
type CheckRule interface {
|
||||||
Name() string
|
Name() string
|
||||||
Description() string
|
Description() string
|
||||||
Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) CheckState
|
Evaluate(ctx context.Context, obs ObservationGetter, opts CheckerOptions) []CheckState
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRuleWithOptions is an optional interface that rules can implement
|
// CheckRuleWithOptions is an optional interface that rules can implement
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue