Add usescases to handle test plugins

This commit is contained in:
nemunaire 2025-09-19 15:24:08 +09:00
commit 8704952d6f
9 changed files with 213 additions and 14 deletions

View file

@ -0,0 +1,78 @@
// 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 controller
import (
"net/http"
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api/middleware"
"git.happydns.org/happyDomain/model"
)
type TestPluginController struct {
testPluginService happydns.TestPluginUsecase
}
func NewTestPluginController(testPluginService happydns.TestPluginUsecase) *TestPluginController {
return &TestPluginController{
testPluginService,
}
}
func (uc *TestPluginController) TestPluginHandler(c *gin.Context) {
pname := c.Param("pname")
plugin, err := uc.testPluginService.GetTestPlugin(pname)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, happydns.ErrorResponse{Message: "Plugin not found"})
return
}
c.Set("plugin", plugin)
c.Next()
}
func (uc *TestPluginController) ListTestPlugins(c *gin.Context) {
plugins, err := uc.testPluginService.ListTestPlugins()
if err != nil {
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
return
}
ret := map[string]happydns.PluginVersionInfo{}
for _, p := range plugins {
pnames := p.PluginEnvName()
ret[pnames[0]] = p.Version()
}
happydns.ApiResponse(c, ret, nil)
}
func (uc *TestPluginController) GetTestPluginStatus(c *gin.Context) {
plugin := c.MustGet("plugin").(happydns.TestPlugin)
c.JSON(http.StatusOK, plugin.Version())
}

View file

@ -0,0 +1,52 @@
// 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 route
import (
"github.com/gin-gonic/gin"
"git.happydns.org/happyDomain/internal/api-admin/controller"
"git.happydns.org/happyDomain/internal/storage"
"git.happydns.org/happyDomain/model"
)
func declarePluginsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies, store storage.Storage) {
apiPluginsRoutes := router.Group("/plugins")
declareTestPluginsRoutes(apiPluginsRoutes, dependancies)
}
func declareTestPluginsRoutes(router *gin.RouterGroup, dependancies happydns.UsecaseDependancies) {
tpc := controller.NewTestPluginController(dependancies.TestPluginUsecase())
apiTestPluginsRoutes := router.Group("/tests")
apiTestPluginsRoutes.GET("", tpc.ListTestPlugins)
apiTestPluginRoutes := apiTestPluginsRoutes.Group("/:pname")
apiTestPluginRoutes.Use(tpc.TestPluginHandler)
apiTestPluginRoutes.GET("", tpc.GetTestPluginStatus)
//apiTestPluginRoutes.POST("", tpc.ChangeTestPluginStatus)
//apiTestPluginRoutes.GET("/options", tpc.ListTestPluginOptions)
//apiTestPluginRoutes.PUT("/options", tpc.ChangeTestPluginOptions)
}

View file

@ -34,6 +34,7 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.Engine, s storage.Storage,
declareBackupRoutes(cfg, apiRoutes, s)
declareDomainRoutes(apiRoutes, dependancies, s)
declarePluginsRoutes(apiRoutes, dependancies, s)
declareProviderRoutes(apiRoutes, dependancies, s)
declareSessionsRoutes(cfg, apiRoutes, s)
declareUserAuthsRoutes(apiRoutes, dependancies, s)

View file

@ -40,6 +40,7 @@ import (
domainUC "git.happydns.org/happyDomain/internal/usecase/domain"
domainlogUC "git.happydns.org/happyDomain/internal/usecase/domain_log"
"git.happydns.org/happyDomain/internal/usecase/orchestrator"
pluginUC "git.happydns.org/happyDomain/internal/usecase/plugin"
providerUC "git.happydns.org/happyDomain/internal/usecase/provider"
serviceUC "git.happydns.org/happyDomain/internal/usecase/service"
sessionUC "git.happydns.org/happyDomain/internal/usecase/session"
@ -63,6 +64,7 @@ type Usecases struct {
session happydns.SessionUsecase
service happydns.ServiceUsecase
serviceSpecs happydns.ServiceSpecsUsecase
testPlugin happydns.TestPluginUsecase
user happydns.UserUsecase
zone happydns.ZoneUsecase
zoneService happydns.ZoneServiceUsecase
@ -77,7 +79,7 @@ type App struct {
router *gin.Engine
srv *http.Server
insights *insightsCollector
plugins happydns.PluginManger
plugins happydns.PluginManager
store storage.Storage
usecases Usecases
}
@ -138,6 +140,10 @@ func (a *App) SessionUsecase() happydns.SessionUsecase {
return a.usecases.session
}
func (a *App) TestPluginUsecase() happydns.TestPluginUsecase {
return a.usecases.testPlugin
}
func (a *App) UserUsecase() happydns.UserUsecase {
return a.usecases.user
}
@ -167,9 +173,9 @@ func NewApp(cfg *happydns.Options) *App {
app.initStorageEngine()
app.initNewsletter()
app.initInsights()
app.initPlugins()
app.initUsecases()
app.setupRouter()
app.initPlugins()
return app
}
@ -182,9 +188,9 @@ func NewAppWithStorage(cfg *happydns.Options, store storage.Storage) *App {
app.initMailer()
app.initNewsletter()
app.initPlugins()
app.initUsecases()
app.setupRouter()
app.initPlugins()
return app
}
@ -273,6 +279,7 @@ func (app *App) initUsecases() {
app.usecases.authUser = authUserService
app.usecases.resolver = usecase.NewResolverUsecase(app.cfg)
app.usecases.session = sessionService
app.usecases.testPlugin = pluginUC.NewTestPluginUsecase(app.cfg, app.plugins)
app.usecases.orchestrator = orchestrator.NewOrchestrator(
domainLogService,

View file

@ -33,9 +33,10 @@ import (
)
func (a *App) initPlugins() error {
a.plugins = PluginManger{
manager := PluginManger{
testsIdx: map[string]happydns.TestPlugin{},
}
a.plugins = &manager
var ret error
@ -53,7 +54,7 @@ func (a *App) initPlugins() error {
fname := path.Join(directory, file.Name())
err = a.plugins.loadPlugin(fname)
err = manager.loadPlugin(fname)
if err != nil {
ret = errors.Join(ret, fmt.Errorf("unable to load plugin %q: %w", fname, err))
}
@ -84,27 +85,27 @@ func (m *PluginManger) loadPlugin(fname string) error {
return err
}
m.plugins = append(m.plugins, myplugin)
m.tests = append(m.tests, myplugin)
// Index the plugin by its names
pluginNames := myplugin.PluginEnvName()
for _, name := range pluginNames {
if p, exists := m.pluginsIdx[name]; exists {
if p, exists := m.testsIdx[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
}
m.pluginsIdx[name] = myplugin
m.testsIdx[name] = myplugin
}
log.Printf("Plugin %s loaded (version %s)", myplugin.Version().Name, myplugin.Version().Version)
return nil
}
func (m *PluginManger) GetTestPlugins() ([]happydns.TestPlugin, error) {
func (m *PluginManger) GetTestPlugins() []happydns.TestPlugin {
return m.tests
}
func (m *PluginManger) GetTestPluginsIndex() (map[string]happydns.TestPlugin, error) {
func (m *PluginManger) GetTestPluginsIndex() map[string]happydns.TestPlugin {
return m.testsIdx
}

View file

@ -0,0 +1,52 @@
// 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 plugin
import (
"fmt"
"git.happydns.org/happyDomain/model"
)
type testPluginUsecase struct {
config *happydns.Options
manager happydns.PluginManager
}
func NewTestPluginUsecase(cfg *happydns.Options, manager happydns.PluginManager) happydns.TestPluginUsecase {
return &testPluginUsecase{
config: cfg,
manager: manager,
}
}
func (tu *testPluginUsecase) GetTestPlugin(pname string) (happydns.TestPlugin, error) {
if plugin, ok := tu.manager.GetTestPluginsIndex()[pname]; !ok {
return nil, fmt.Errorf("unable to find plugin named %q", pname)
} else {
return plugin, nil
}
}
func (tu *testPluginUsecase) ListTestPlugins() ([]happydns.TestPlugin, error) {
return tu.manager.GetTestPlugins(), nil
}

View file

@ -30,11 +30,13 @@ const (
type PluginResultStatus int
type PluginOptions map[string]interface{}
type TestPlugin interface {
PluginEnvName() []string
Version() PluginVersionInfo
RunTest(options map[string]interface{}, meta map[string]string) (*PluginResult, error)
RunTest(options PluginOptions, meta map[string]string) (*PluginResult, error)
}
type PluginVersionInfo struct {
@ -57,6 +59,11 @@ type PluginResult struct {
}
type PluginManager interface {
GetTestPlugins() ([]TestPlugin, error)
GetTestPluginsIndex() (map[string]TestPlugin, error)
GetTestPlugins() []TestPlugin
GetTestPluginsIndex() map[string]TestPlugin
}
type TestPluginUsecase interface {
GetTestPlugin(string) (TestPlugin, error)
ListTestPlugins() ([]TestPlugin, error)
}

View file

@ -34,6 +34,7 @@ type UsecaseDependancies interface {
ServiceUsecase() ServiceUsecase
ServiceSpecsUsecase() ServiceSpecsUsecase
SessionUsecase() SessionUsecase
TestPluginUsecase() TestPluginUsecase
UserUsecase() UserUsecase
ZoneCorrectionApplierUsecase() ZoneCorrectionApplierUsecase
ZoneImporterUsecase() ZoneImporterUsecase

View file

@ -54,7 +54,7 @@ type FederationTesterResponse struct {
FederationOK bool `json:"FederationOK"`
}
func (p *MatrixTester) RunTest(options map[string]interface{}, meta map[string]string) (*happydns.PluginResult, error) {
func (p *MatrixTester) RunTest(options happydns.PluginOptions, meta map[string]string) (*happydns.PluginResult, error) {
var domain string
if dn, ok := options["domain"]; ok {