Load tests plugins

This commit is contained in:
nemunaire 2025-09-15 16:25:36 +09:00
commit 6a68bafdbb
5 changed files with 160 additions and 0 deletions

View file

@ -77,6 +77,8 @@ type App struct {
router *gin.Engine
srv *http.Server
insights *insightsCollector
plugins []happydns.TestPlugin
pluginsIdx map[string]happydns.TestPlugin
store storage.Storage
usecases Usecases
}
@ -168,6 +170,7 @@ func NewApp(cfg *happydns.Options) *App {
app.initInsights()
app.initUsecases()
app.setupRouter()
app.LoadPlugins()
return app
}
@ -182,6 +185,7 @@ func NewAppWithStorage(cfg *happydns.Options, store storage.Storage) *App {
app.initNewsletter()
app.initUsecases()
app.setupRouter()
app.LoadPlugins()
return app
}

95
internal/app/plugins.go Normal file
View file

@ -0,0 +1,95 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 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 app
import (
"errors"
"fmt"
"log"
"os"
"path"
"plugin"
"git.happydns.org/happyDomain/model"
)
func (a *App) LoadPlugins() error {
a.pluginsIdx = map[string]happydns.TestPlugin{}
var ret error
for _, directory := range a.cfg.PluginsDirectories {
files, err := os.ReadDir(directory)
if err != nil {
ret = errors.Join(ret, err)
continue
}
for _, file := range files {
if file.IsDir() {
continue
}
fname := path.Join(directory, file.Name())
err = a.loadPlugin(fname)
if err != nil {
ret = errors.Join(ret, fmt.Errorf("unable to load plugin %q: %w", fname, err))
}
}
}
return ret
}
func (a *App) loadPlugin(fname string) error {
p, err := plugin.Open(fname)
if err != nil {
return err
}
newplugin, err := p.Lookup("NewTestPlugin")
if err != nil {
return err
}
myplugin, err := newplugin.(func() (happydns.TestPlugin, error))()
if err != nil {
return err
}
a.plugins = append(a.plugins, myplugin)
// Index the plugin by its names
pluginNames := myplugin.PluginEnvName()
for _, name := range pluginNames {
if p, exists := a.pluginsIdx[name]; exists {
log.Printf("Plugin name conflict: the plugin at %q tries to register the name %q but it's already registered by %q", fname, name, p.Version().Name)
continue
}
a.pluginsIdx[name] = myplugin
}
log.Printf("Plugin %s loaded (version %s)", myplugin.Version().Name, myplugin.Version().Version)
return nil
}

View file

@ -57,6 +57,8 @@ func declareFlags(o *happydns.Options) {
flag.StringVar(&o.MailSMTPPassword, "mail-smtp-password", o.MailSMTPPassword, "Password associated with the given username for SMTP authentication")
flag.BoolVar(&o.MailSMTPTLSSNoVerify, "mail-smtp-tls-no-verify", o.MailSMTPTLSSNoVerify, "Do not verify certificate validity on SMTP connection")
flag.Var(&ArrayArgs{&o.PluginsDirectories}, "plugins-directory", "Path to a directory containing plugins (can be repeated multiple times)")
// Others flags are declared in some other files likes sources, storages, ... when they need specials configurations
}

View file

@ -92,6 +92,8 @@ type Options struct {
MailSMTPTLSSNoVerify bool
OIDCClients []OIDCSettings
PluginsDirectories []string
}
// GetBaseURL returns the full url to the absolute ExternalURL, including BaseURL.

57
model/plugin.go Normal file
View file

@ -0,0 +1,57 @@
// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2025 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 happydns
const (
PluginResultStatusKO PluginResultStatus = iota
PluginResultStatusWarn
PluginResultStatusInfo
PluginResultStatusOK
)
type PluginResultStatus int
type TestPlugin interface {
PluginEnvName() []string
Version() PluginVersionInfo
RunTest(options map[string]interface{}, meta map[string]string) (*PluginResult, error)
}
type PluginVersionInfo struct {
Name string `json:"name"`
Version string `json:"version"`
AvailableOn PluginAvailability `json:"availableOn"`
}
type PluginAvailability struct {
ApplyToDomain bool `json:"applyToDomain,omitempty"`
ApplyToService bool `json:"applyToDomain,omitempty"`
LimitToProviders []string `json:"limitToProviders,omitempty"`
LimitToServices []string `json:"limitToServices,omitempty"`
}
type PluginResult struct {
Status PluginResultStatus `json:"status"`
StatusLine string `json:"statusLine,omitempty"`
Report interface{} `json:"report"`
}