registry: refuse duplicate registrations with a warning

RegisterChecker and RegisterObservationProvider previously overwrote
existing entries silently, which lets a misconfigured plugin shadow a
built-in (or another plugin) without any signal. Refuse the duplicate
and keep the existing entry instead.

RegisterExternalizableChecker now performs the dedup check before
appending the "endpoint" AdminOpt, so a rejected re-registration no
longer mutates the live definition.
This commit is contained in:
nemunaire 2026-04-08 20:42:21 +07:00
commit 8e2ba83a0d
2 changed files with 152 additions and 2 deletions

View file

@ -27,8 +27,16 @@ var checkerRegistry = map[string]*CheckerDefinition{}
// keyed by ObservationKey.
var observationProviderRegistry = map[ObservationKey]ObservationProvider{}
// RegisterChecker registers a checker definition globally.
// RegisterChecker registers a checker definition globally. A second
// registration under the same ID is refused with a warning rather than
// silently overwriting the previous entry: in production this almost
// always indicates a deployment mistake (two plugins shipping the same
// checker, or a plugin shadowing a built-in).
func RegisterChecker(c *CheckerDefinition) {
if _, exists := checkerRegistry[c.ID]; exists {
log.Printf("Warning: checker %q is already registered; ignoring duplicate registration", c.ID)
return
}
log.Println("Registering new checker:", c.ID)
c.BuildRulesInfo()
checkerRegistry[c.ID] = c
@ -38,7 +46,16 @@ func RegisterChecker(c *CheckerDefinition) {
// delegated to a remote HTTP endpoint. It appends an "endpoint" AdminOpt
// so the administrator can optionally configure a remote URL.
// When the endpoint is left empty, the checker runs locally as usual.
//
// The duplicate check happens before the AdminOpt append so that a
// rejected second registration does not mutate the in-memory definition
// of the already-registered checker (which a caller might still hold a
// pointer to).
func RegisterExternalizableChecker(c *CheckerDefinition) {
if _, exists := checkerRegistry[c.ID]; exists {
log.Printf("Warning: checker %q is already registered; ignoring duplicate registration", c.ID)
return
}
c.Options.AdminOpts = append(c.Options.AdminOpts,
CheckerOptionDocumentation{
Id: "endpoint",
@ -53,8 +70,15 @@ func RegisterExternalizableChecker(c *CheckerDefinition) {
}
// RegisterObservationProvider registers an observation provider globally.
// A second registration under the same key is refused with a warning for
// the same reason as RegisterChecker.
func RegisterObservationProvider(p ObservationProvider) {
observationProviderRegistry[p.Key()] = p
key := p.Key()
if _, exists := observationProviderRegistry[key]; exists {
log.Printf("Warning: observation provider %q is already registered; ignoring duplicate registration", key)
return
}
observationProviderRegistry[key] = p
}
// GetCheckers returns all registered checker definitions.