happyDomain/internal/provider/registry.go
Pierre-Olivier Mercier e58a585e2f providers: load external provider plugins from .so files
Add a NewProviderPlugin loader alongside the existing checker plugin
loader. The factory returns a ProviderCreatorFunc / ProviderInfos pair,
which is registered through internal/provider.RegisterProvider so plugin
providers appear in the registry like the built-in ones.
2026-04-10 17:01:09 +07:00

99 lines
3.9 KiB
Go

// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package provider
import (
"fmt"
"log"
"reflect"
"slices"
"git.happydns.org/happyDomain/model"
)
// providerRegistry stores all existing Provider in happyDNS.
//
// The map is intentionally unguarded: all writes (RegisterProvider /
// RegisterProviderAs) happen from App.initPlugins() at startup, before any
// usecase or HTTP handler can read it — see internal/app/app.go. From that
// point on the registry is read-only for the rest of the process lifetime,
// so concurrent reads are safe without locking. Any future code path that
// needs to mutate it after startup must introduce its own synchronisation.
var providerRegistry = map[string]happydns.ProviderCreator{}
// RegisterProvider registers a provider definition globally under the
// unqualified Go type name of the value returned by creator(). This is the
// historical entry point used by built-in providers; the persisted
// happydns.Provider.Type field stores this same unqualified name, so
// changing the keying scheme here would break existing data.
func RegisterProvider(creator happydns.ProviderCreatorFunc, infos happydns.ProviderInfos) {
provider := creator()
baseType := reflect.Indirect(reflect.ValueOf(provider)).Type()
RegisterProviderAs(baseType.Name(), creator, infos)
}
// RegisterProviderAs registers a provider definition globally under the
// caller-supplied name. It exists so that plugin loaders can pick a
// fully-qualified name (typically "package.Type") and avoid silently
// overwriting a built-in or another plugin that happens to expose a
// provider struct with the same short name.
//
// A second registration under an existing name is refused with a loud
// warning rather than overwriting the previous entry: in production this
// almost always indicates a deployment mistake (two plugins shipping the
// same provider, or a plugin shadowing a built-in).
func RegisterProviderAs(name string, creator happydns.ProviderCreatorFunc, infos happydns.ProviderInfos) {
if _, exists := providerRegistry[name]; exists {
log.Printf("Warning: provider %q is already registered; ignoring duplicate registration", name)
return
}
log.Println("Registering new provider:", name)
providerRegistry[name] = happydns.ProviderCreator{
Creator: creator,
Infos: infos,
}
}
// GetProviders returns all registered provider definitions.
func GetProviders() map[string]happydns.ProviderCreator {
return providerRegistry
}
// ProviderHasCapability checks if the registered provider type has the given capability.
func ProviderHasCapability(provider *happydns.Provider, capability string) bool {
creator, ok := providerRegistry[provider.Type]
if !ok {
return false
}
return slices.Contains(creator.Infos.Capabilities, capability)
}
// FindProvider returns the Provider corresponding to the given name, or an error if it doesn't exist.
func FindProvider(name string) (happydns.ProviderBody, error) {
src, ok := providerRegistry[name]
if !ok {
return nil, fmt.Errorf("Unable to find corresponding provider for `%s`.", name)
}
return src.Creator(), nil
}