Expose one rule per source with rule-scoped options
Each registered Source now becomes its own CheckRule (name = source ID) implementing CheckRuleWithOptions, so the host can toggle blacklists individually and the per-source option fields show up under the rule that owns them instead of one flat global option list. Collect honours the host's per-rule enable map (via the SDK's RuleEnabled context helper) and skips the network call for disabled sources entirely, not just their evaluation.
This commit is contained in:
parent
6719e21b51
commit
ce59a976d5
5 changed files with 49 additions and 51 deletions
|
|
@ -38,6 +38,11 @@ func (p *blacklistProvider) Collect(ctx context.Context, opts sdk.CheckerOptions
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i, s := range sources {
|
for i, s := range sources {
|
||||||
|
// The host disables a source by disabling its rule (rule name == source ID).
|
||||||
|
// Skip the network call entirely; Evaluate is short-circuited host-side.
|
||||||
|
if !sdk.RuleEnabled(ctx, s.ID()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(i int, s Source) {
|
go func(i int, s Source) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ import (
|
||||||
// Version is overridden at link time by the standalone or plugin entrypoints.
|
// Version is overridden at link time by the standalone or plugin entrypoints.
|
||||||
var Version = "built-in"
|
var Version = "built-in"
|
||||||
|
|
||||||
// Definition assembles the checker definition by aggregating each
|
// Definition assembles the checker definition. Per-source option fields
|
||||||
// registered Source's options into the SDK's audience-grouped layout.
|
// live on each per-source rule (CheckRuleWithOptions); the global Options
|
||||||
// Adding a source automatically adds its option fields here: no edit
|
// only carries the shared domain target.
|
||||||
// to this file needed.
|
|
||||||
func Definition() *sdk.CheckerDefinition {
|
func Definition() *sdk.CheckerDefinition {
|
||||||
opts := sdk.CheckerOptionsDocumentation{
|
opts := sdk.CheckerOptionsDocumentation{
|
||||||
DomainOpts: []sdk.CheckerOptionDocumentation{
|
DomainOpts: []sdk.CheckerOptionDocumentation{
|
||||||
|
|
@ -23,11 +22,6 @@ func Definition() *sdk.CheckerDefinition {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, s := range Sources() {
|
|
||||||
o := s.Options()
|
|
||||||
opts.AdminOpts = append(opts.AdminOpts, o.Admin...)
|
|
||||||
opts.UserOpts = append(opts.UserOpts, o.User...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &sdk.CheckerDefinition{
|
return &sdk.CheckerDefinition{
|
||||||
ID: "blacklist",
|
ID: "blacklist",
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,39 @@ import (
|
||||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rules returns the rule set surfaced to happyDomain. After the
|
// Rules returns one rule per registered Source. The host can enable or
|
||||||
// registry refactor we expose a single, generic rule that emits one
|
// disable each independently, and each rule owns the option fields its
|
||||||
// CheckState per source result: the per-source verdict lives in
|
// Source contributes (CheckRuleWithOptions). The rule name is the
|
||||||
// CheckState.Subject (the source name) and CheckState.Code carries the
|
// Source ID; downstream a disabled rule both skips evaluation and
|
||||||
// canonical hit / clean / disabled / error flavour.
|
// skips the underlying network call in Collect.
|
||||||
func Rules() []sdk.CheckRule {
|
func Rules() []sdk.CheckRule {
|
||||||
return []sdk.CheckRule{&sourceRule{}}
|
srcs := Sources()
|
||||||
|
rules := make([]sdk.CheckRule, 0, len(srcs))
|
||||||
|
for _, s := range srcs {
|
||||||
|
rules = append(rules, &sourceRule{src: s})
|
||||||
|
}
|
||||||
|
return rules
|
||||||
}
|
}
|
||||||
|
|
||||||
type sourceRule struct{}
|
type sourceRule struct {
|
||||||
|
src Source
|
||||||
func (*sourceRule) Name() string { return "source_listed" }
|
|
||||||
func (*sourceRule) Description() string {
|
|
||||||
return "Emits one state per reputation source: Critical/Warning when the source flags the domain, OK when clean, Info when the source is disabled, and Warning on transient query errors."
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
func (r *sourceRule) Name() string { return r.src.ID() }
|
||||||
|
|
||||||
|
func (r *sourceRule) Description() string {
|
||||||
|
return fmt.Sprintf("%s reputation lookup. Emits Critical/Warning when the source flags the domain, OK when clean, Info when the source is disabled or not configured, and Warning on transient query errors.", r.src.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sourceRule) Options() sdk.CheckerOptionsDocumentation {
|
||||||
|
o := r.src.Options()
|
||||||
|
return sdk.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: o.Admin,
|
||||||
|
UserOpts: o.User,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
||||||
var data BlacklistData
|
var data BlacklistData
|
||||||
if err := obs.Get(ctx, ObservationKeyBlacklist, &data); err != nil {
|
if err := obs.Get(ctx, ObservationKeyBlacklist, &data); err != nil {
|
||||||
return []sdk.CheckState{{
|
return []sdk.CheckState{{
|
||||||
|
|
@ -33,25 +49,19 @@ func (*sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.Results) == 0 {
|
var out []sdk.CheckState
|
||||||
return []sdk.CheckState{{
|
for _, res := range data.Results {
|
||||||
Status: sdk.StatusInfo, Message: "No reputation sources registered.",
|
if res.SourceID != r.src.ID() {
|
||||||
Code: "blacklist_no_sources",
|
continue
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
byID := make(map[string]Source, len(Sources()))
|
|
||||||
for _, s := range Sources() {
|
|
||||||
byID[s.ID()] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make([]sdk.CheckState, 0, len(data.Results))
|
|
||||||
for _, r := range data.Results {
|
|
||||||
src, ok := byID[r.SourceID]
|
|
||||||
if !ok {
|
|
||||||
src = unknownSource{}
|
|
||||||
}
|
}
|
||||||
out = append(out, evaluateOne(r, src))
|
out = append(out, evaluateOne(res, r.src))
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return []sdk.CheckState{{
|
||||||
|
Status: sdk.StatusUnknown,
|
||||||
|
Message: r.src.Name() + ": no result (source skipped or disabled).",
|
||||||
|
Code: "source_disabled",
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
@ -105,17 +115,6 @@ func evaluateOne(r SourceResult, src Source) sdk.CheckState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unknownSource is a sentinel used when a SourceResult references a source ID
|
|
||||||
// that is no longer in the registry. Evaluate always returns (false, "").
|
|
||||||
type unknownSource struct{}
|
|
||||||
|
|
||||||
func (unknownSource) ID() string { return "" }
|
|
||||||
func (unknownSource) Name() string { return "unknown" }
|
|
||||||
func (unknownSource) Options() SourceOptions { return SourceOptions{} }
|
|
||||||
func (unknownSource) Query(_ context.Context, _, _ string, _ sdk.CheckerOptions) []SourceResult { return nil }
|
|
||||||
func (unknownSource) Diagnose(_ SourceResult) Diagnosis { return Diagnosis{} }
|
|
||||||
func (unknownSource) Evaluate(_ SourceResult) (bool, string) { return false, "" }
|
|
||||||
|
|
||||||
func severityToStatus(sev string) sdk.Status {
|
func severityToStatus(sev string) sdk.Status {
|
||||||
switch sev {
|
switch sev {
|
||||||
case SeverityCrit:
|
case SeverityCrit:
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,6 @@ module git.happydns.org/checker-blacklist
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.happydns.org/checker-sdk-go v1.5.0
|
git.happydns.org/checker-sdk-go v1.8.0
|
||||||
golang.org/x/net v0.34.0
|
golang.org/x/net v0.34.0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -1,4 +1,4 @@
|
||||||
git.happydns.org/checker-sdk-go v1.5.0 h1:5uD5Cm6xJ+lwnhbJ09iCXGHbYS9zRh+Yh0NeBHkAPBY=
|
git.happydns.org/checker-sdk-go v1.8.0 h1:2lhcSc16rnCaszdQi1nerszb2c3fVh5XNS11pLrXuK4=
|
||||||
git.happydns.org/checker-sdk-go v1.5.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
git.happydns.org/checker-sdk-go v1.8.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue