Implement plugin options retrieval

This commit is contained in:
nemunaire 2025-09-23 12:45:40 +09:00
commit bf63fa5708
12 changed files with 624 additions and 5 deletions

View file

@ -55,6 +55,22 @@ func (uc *TestPluginController) TestPluginHandler(c *gin.Context) {
c.Next()
}
// TestPluginOptionHandler is a middleware that retrieves a specific plugin option and sets it in the context.
func (uc *TestPluginController) TestPluginOptionHandler(c *gin.Context) {
pname := c.Param("pname")
optname := c.Param("optname")
opts, err := uc.testPluginService.GetTestPluginOptions(pname, nil, nil, nil)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
return
}
c.Set("option", (*opts)[optname])
c.Next()
}
// ListTestPlugins retrieves all available test plugins.
//
// @Summary List all test plugins
@ -98,5 +114,140 @@ func (uc *TestPluginController) ListTestPlugins(c *gin.Context) {
func (uc *TestPluginController) GetTestPluginStatus(c *gin.Context) {
plugin := c.MustGet("plugin").(happydns.TestPlugin)
c.JSON(http.StatusOK, plugin.Version())
c.JSON(http.StatusOK, happydns.PluginStatus{
PluginVersionInfo: plugin.Version(),
Opts: plugin.AvailableOptions(),
})
}
// GetTestPluginOptions retrieves all options for a test plugin.
//
// @Summary Get test plugin options
// @Schemes
// @Description Retrieves all configuration options for a specific test plugin.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Success 200 {object} happydns.PluginOptions "Plugin options as key-value pairs"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options [get]
func (uc *TestPluginController) GetTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
opts, err := uc.testPluginService.GetTestPluginOptions(pname, nil, nil, nil)
happydns.ApiResponse(c, opts, err)
}
// AddTestPluginOptions adds or overwrites specific options for a test plugin.
//
// @Summary Add test plugin options
// @Schemes
// @Description Adds or overwrites specific configuration options for a test plugin without affecting other options.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Param body body happydns.SetPluginOptionsRequest true "Options to add or overwrite"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options [post]
func (uc *TestPluginController) AddTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = uc.testPluginService.OverwriteSomeTestPluginOptions(pname, nil, nil, nil, req.Options)
happydns.ApiResponse(c, true, err)
}
// ChangeTestPluginOptions replaces all options for a test plugin.
//
// @Summary Replace test plugin options
// @Schemes
// @Description Replaces all configuration options for a test plugin with the provided options.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Param body body happydns.SetPluginOptionsRequest true "New complete set of options"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options [put]
func (uc *TestPluginController) ChangeTestPluginOptions(c *gin.Context) {
pname := c.Param("pname")
var req happydns.SetPluginOptionsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
err = uc.testPluginService.SetTestPluginOptions(pname, nil, nil, nil, req.Options)
happydns.ApiResponse(c, true, err)
}
// GetTestPluginOption retrieves a specific option value for a test plugin.
//
// @Summary Get test plugin option
// @Schemes
// @Description Retrieves the value of a specific configuration option for a test plugin.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Param optname path string true "Option name"
// @Success 200 {object} object "Option value (type varies)"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options/{optname} [get]
func (uc *TestPluginController) GetTestPluginOption(c *gin.Context) {
opt := c.MustGet("option")
happydns.ApiResponse(c, opt, nil)
}
// SetTestPluginOption sets or updates a specific option value for a test plugin.
//
// @Summary Set test plugin option
// @Schemes
// @Description Sets or updates the value of a specific configuration option for a test plugin.
// @Tags plugins
// @Accept json
// @Produce json
// @Param pname path string true "Plugin name"
// @Param optname path string true "Option name"
// @Param body body object true "Option value (type varies by option)"
// @Success 200 {object} bool "Success status"
// @Failure 400 {object} happydns.ErrorResponse "Invalid request body"
// @Failure 404 {object} happydns.ErrorResponse "Plugin not found"
// @Failure 500 {object} happydns.ErrorResponse "Internal server error"
// @Router /plugins/tests/{pname}/options/{optname} [put]
func (uc *TestPluginController) SetTestPluginOption(c *gin.Context) {
pname := c.Param("pname")
optname := c.Param("optname")
var req interface{}
err := c.ShouldBindJSON(&req)
if err != nil {
middleware.ErrorResponse(c, http.StatusBadRequest, err)
return
}
po := happydns.PluginOptions{}
po[optname] = req
err = uc.testPluginService.OverwriteSomeTestPluginOptions(pname, nil, nil, nil, po)
happydns.ApiResponse(c, true, err)
}

View file

@ -47,6 +47,12 @@ func declareTestPluginsRoutes(router *gin.RouterGroup, dependancies happydns.Use
apiTestPluginRoutes.GET("", tpc.GetTestPluginStatus)
//apiTestPluginRoutes.POST("", tpc.ChangeTestPluginStatus)
//apiTestPluginRoutes.GET("/options", tpc.ListTestPluginOptions)
//apiTestPluginRoutes.PUT("/options", tpc.ChangeTestPluginOptions)
apiTestPluginRoutes.GET("/options", tpc.GetTestPluginOptions)
apiTestPluginRoutes.POST("/options", tpc.AddTestPluginOptions)
apiTestPluginRoutes.PUT("/options", tpc.ChangeTestPluginOptions)
apiTestPluginOptionsRoutes := apiTestPluginRoutes.Group("/options/:optname")
apiTestPluginOptionsRoutes.Use(tpc.TestPluginOptionHandler)
apiTestPluginOptionsRoutes.GET("", tpc.GetTestPluginOption)
apiTestPluginOptionsRoutes.PUT("", tpc.SetTestPluginOption)
}

View file

@ -303,7 +303,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.testPlugin = pluginUC.NewTestPluginUsecase(app.cfg, app.plugins, app.store)
app.usecases.orchestrator = orchestrator.NewOrchestrator(
domainLogService,

View file

@ -44,6 +44,7 @@ type InMemoryStorage struct {
domainLogs map[string]*happydns.DomainLogWithDomainId
domainLogsByDomains map[string][]*happydns.Identifier
providers map[string]*happydns.ProviderMessage
pluginsCfg map[string]*happydns.PluginOptions
sessions map[string]*happydns.Session
users map[string]*happydns.User
usersByEmail map[string]*happydns.User

View file

@ -26,6 +26,7 @@ import (
"git.happydns.org/happyDomain/internal/usecase/domain"
"git.happydns.org/happyDomain/internal/usecase/domain_log"
"git.happydns.org/happyDomain/internal/usecase/insight"
"git.happydns.org/happyDomain/internal/usecase/plugin"
"git.happydns.org/happyDomain/internal/usecase/provider"
"git.happydns.org/happyDomain/internal/usecase/session"
"git.happydns.org/happyDomain/internal/usecase/user"
@ -43,6 +44,7 @@ type Storage interface {
domain.DomainStorage
domainlog.DomainLogStorage
insight.InsightStorage
plugin.PluginStorage
provider.ProviderStorage
session.SessionStorage
user.UserStorage

View file

@ -0,0 +1,185 @@
// 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 database
import (
"errors"
"fmt"
"strings"
"git.happydns.org/happyDomain/model"
)
func (s *KVStorage) ListAllPluginConfigurations() (happydns.Iterator[happydns.PluginOptions], error) {
iter := s.db.Search("plugincfg-")
return NewKVIterator[happydns.PluginOptions](s.db, iter), nil
}
func buildPluginKey(pname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) string {
u := ""
if user != nil {
u = user.String()
}
d := ""
if domain != nil {
d = domain.String()
}
s := ""
if service != nil {
s = service.String()
}
return strings.Join([]string{pname, u, d, s}, "/")
}
func keyToPositional(key string, opts *happydns.PluginOptions) (*happydns.PluginOptionsPositional, error) {
tmp := strings.Split(key, "/")
if len(tmp) < 4 {
return nil, fmt.Errorf("malformed plugin configuration key, got %q", key)
}
pname := tmp[0]
var userid *happydns.Identifier
if len(tmp[1]) > 0 {
u, err := happydns.NewIdentifierFromString(tmp[1])
if err != nil {
return nil, err
}
userid = &u
}
var domainid *happydns.Identifier
if len(tmp[2]) > 0 {
d, err := happydns.NewIdentifierFromString(tmp[2])
if err != nil {
return nil, err
}
domainid = &d
}
var serviceid *happydns.Identifier
if len(tmp[3]) > 0 {
s, err := happydns.NewIdentifierFromString(tmp[3])
if err != nil {
return nil, err
}
serviceid = &s
}
return &happydns.PluginOptionsPositional{
PluginName: pname,
UserId: userid,
DomainId: domainid,
ServiceId: serviceid,
Options: *opts,
}, nil
}
func (s *KVStorage) ListPluginConfiguration(pname string) (configs []*happydns.PluginOptionsPositional, err error) {
iter := s.db.Search("plugincfg-" + pname + "/")
defer iter.Release()
for iter.Next() {
var p happydns.PluginOptions
e := s.db.DecodeData(iter.Value(), &p)
if e != nil {
err = errors.Join(err, e)
continue
}
opts, e := keyToPositional(strings.TrimPrefix(iter.Key(), "plugincfg-"), &p)
if e != nil {
err = errors.Join(err, e)
continue
}
configs = append(configs, opts)
}
return
}
func (s *KVStorage) GetPluginConfiguration(pname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) (configs []*happydns.PluginOptionsPositional, err error) {
iter := s.db.Search("plugincfg-" + pname + "/")
defer iter.Release()
for iter.Next() {
var p happydns.PluginOptions
e := s.db.DecodeData(iter.Value(), &p)
if e != nil {
err = errors.Join(err, e)
continue
}
opts, e := keyToPositional(strings.TrimPrefix(iter.Key(), "plugincfg-"), &p)
if e != nil {
err = errors.Join(err, e)
continue
}
// Match logic:
// - When parameter is nil: match ONLY configs with nil ID (requesting specific scope)
// - When parameter is not nil: match configs with nil ID (admin-level) OR matching ID
matchUser := (user == nil && opts.UserId == nil) ||
(user != nil && (opts.UserId == nil || opts.UserId.Equals(*user)))
matchDomain := (domain == nil && opts.DomainId == nil) ||
(domain != nil && (opts.DomainId == nil || opts.DomainId.Equals(*domain)))
matchService := (service == nil && opts.ServiceId == nil) ||
(service != nil && (opts.ServiceId == nil || opts.ServiceId.Equals(*service)))
if matchUser && matchDomain && matchService {
configs = append(configs, opts)
}
}
return
}
func (s *KVStorage) UpdatePluginConfiguration(pname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier, opts happydns.PluginOptions) error {
return s.db.Put(fmt.Sprintf("plugincfg-%s", buildPluginKey(pname, user, domain, service)), opts)
}
func (s *KVStorage) DeletePluginConfiguration(pname string, user *happydns.Identifier, domain *happydns.Identifier, service *happydns.Identifier) error {
return s.db.Delete(fmt.Sprintf("plugincfg-%s", buildPluginKey(pname, user, domain, service)))
}
func (s *KVStorage) ClearPluginConfigurations() error {
iter := s.db.Search("plugincfg-")
defer iter.Release()
for iter.Next() {
err := s.db.Delete(iter.Key())
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,46 @@
// 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 (
"git.happydns.org/happyDomain/model"
)
type PluginStorage interface {
// ListAllPluginConfigurations retrieves the list of known Providers.
ListAllPluginConfigurations() (happydns.Iterator[happydns.PluginOptions], error)
// ListPluginConfiguration retrieves all providers own by the given User.
ListPluginConfiguration(string) ([]*happydns.PluginOptionsPositional, error)
// GetPluginConfiguration retrieves the full Provider with the given identifier and owner.
GetPluginConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier) ([]*happydns.PluginOptionsPositional, error)
// UpdatePluginConfiguration updates the fields of the given Provider.
UpdatePluginConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier, happydns.PluginOptions) error
// DeletePluginConfiguration removes the given Provider from the database.
DeletePluginConfiguration(string, *happydns.Identifier, *happydns.Identifier, *happydns.Identifier) error
// ClearPluginConfigurations deletes all Providers present in the database.
ClearPluginConfigurations() error
}

View file

@ -23,6 +23,7 @@ package plugin
import (
"fmt"
"sort"
"git.happydns.org/happyDomain/model"
)
@ -30,12 +31,14 @@ import (
type testPluginUsecase struct {
config *happydns.Options
manager happydns.PluginManager
store PluginStorage
}
func NewTestPluginUsecase(cfg *happydns.Options, manager happydns.PluginManager) happydns.TestPluginUsecase {
func NewTestPluginUsecase(cfg *happydns.Options, manager happydns.PluginManager, store PluginStorage) happydns.TestPluginUsecase {
return &testPluginUsecase{
config: cfg,
manager: manager,
store: store,
}
}
@ -48,6 +51,84 @@ func (tu *testPluginUsecase) GetTestPlugin(pname string) (happydns.TestPlugin, e
}
}
type ByOptionPosition []*happydns.PluginOptionsPositional
func (a ByOptionPosition) Len() int { return len(a) }
func (a ByOptionPosition) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByOptionPosition) Less(i, j int) bool {
if a[i].PluginName != a[j].PluginName {
return a[i].PluginName < a[j].PluginName
}
if res := compareIdentifiers(a[i].UserId, a[j].UserId); res != 0 {
return res < 0
}
if res := compareIdentifiers(a[i].DomainId, a[j].DomainId); res != 0 {
return res < 0
}
if res := compareIdentifiers(a[i].ServiceId, a[j].ServiceId); res != 0 {
return res < 0
}
return false
}
func compareIdentifiers(a, b *happydns.Identifier) int {
if a == nil && b == nil {
return 0
}
if a == nil {
return -1
}
if b == nil {
return 1
}
if a.Equals(*b) {
return 0
}
return a.Compare(*b)
}
func (tu *testPluginUsecase) GetTestPluginOptions(pname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier) (*happydns.PluginOptions, error) {
configs, err := tu.store.GetPluginConfiguration(pname, userid, domainid, serviceid)
if err != nil {
return nil, err
}
sort.Sort(ByOptionPosition(configs))
opts := make(happydns.PluginOptions)
for _, c := range configs {
for k, v := range c.Options {
opts[k] = v
}
}
return &opts, nil
}
func (tu *testPluginUsecase) ListTestPlugins() ([]happydns.TestPlugin, error) {
return tu.manager.GetTestPlugins(), nil
}
func (tu *testPluginUsecase) SetTestPluginOptions(pname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.PluginOptions) error {
return tu.store.UpdatePluginConfiguration(pname, userid, domainid, serviceid, opts)
}
func (tu *testPluginUsecase) OverwriteSomeTestPluginOptions(pname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.PluginOptions) error {
current, err := tu.GetTestPluginOptions(pname, userid, domainid, serviceid)
if err != nil {
return err
}
for k, v := range opts {
(*current)[k] = v
}
return tu.store.UpdatePluginConfiguration(pname, userid, domainid, serviceid, *current)
}

View file

@ -0,0 +1,85 @@
package plugin_test
import (
"sort"
"testing"
uc "git.happydns.org/happyDomain/internal/usecase/plugin"
"git.happydns.org/happyDomain/model"
)
func TestSortByPluginName(t *testing.T) {
slice := []*happydns.PluginOptionsPositional{
{PluginName: "zeta"},
{PluginName: "alpha"},
{PluginName: "beta"},
}
sort.Sort(uc.ByOptionPosition(slice))
got := []string{slice[0].PluginName, slice[1].PluginName, slice[2].PluginName}
want := []string{"alpha", "beta", "zeta"}
for i := range want {
if got[i] != want[i] {
t.Errorf("expected %v, got %v", want, got)
break
}
}
}
func TestNilBeforeNonNil(t *testing.T) {
uid, _ := happydns.NewRandomIdentifier()
slice := []*happydns.PluginOptionsPositional{
{PluginName: "alpha", UserId: &uid},
{PluginName: "alpha", UserId: nil},
}
sort.Sort(uc.ByOptionPosition(slice))
if slice[0].UserId != nil {
t.Errorf("expected nil UserId first, got %+v", slice[0].UserId)
}
}
func TestDomainIdOrder(t *testing.T) {
did, _ := happydns.NewRandomIdentifier()
slice := []*happydns.PluginOptionsPositional{
{PluginName: "alpha", UserId: nil, DomainId: &did},
{PluginName: "alpha", UserId: nil, DomainId: nil},
}
sort.Sort(uc.ByOptionPosition(slice))
if slice[0].DomainId != nil {
t.Errorf("expected nil DomainId first, got %+v", slice[0].DomainId)
}
}
func TestServiceIdOrder(t *testing.T) {
sid, _ := happydns.NewRandomIdentifier()
slice := []*happydns.PluginOptionsPositional{
{PluginName: "alpha", UserId: nil, DomainId: nil, ServiceId: &sid},
{PluginName: "alpha", UserId: nil, DomainId: nil, ServiceId: nil},
}
sort.Sort(uc.ByOptionPosition(slice))
if slice[0].ServiceId != nil {
t.Errorf("expected nil ServiceId first, got %+v", slice[0].ServiceId)
}
}
func TestStableGrouping(t *testing.T) {
uid, _ := happydns.NewRandomIdentifier()
slice := []*happydns.PluginOptionsPositional{
{PluginName: "alpha", UserId: &uid},
{PluginName: "alpha", UserId: &uid},
}
sort.Sort(uc.ByOptionPosition(slice))
if slice[0].PluginName != slice[1].PluginName {
t.Errorf("expected grouping, got %+v vs %+v", slice[0], slice[1])
}
}

View file

@ -27,6 +27,7 @@ import (
"encoding/base64"
"encoding/gob"
"errors"
"slices"
)
const IDENTIFIER_LEN = 16
@ -55,6 +56,10 @@ func (i Identifier) Equals(other Identifier) bool {
return bytes.Equal(i, other)
}
func (i Identifier) Compare(other Identifier) int {
return slices.Compare(i, other)
}
func (i *Identifier) String() string {
return base64.RawURLEncoding.EncodeToString(*i)
}

View file

@ -32,9 +32,23 @@ type PluginResultStatus int
type PluginOptions map[string]any
type SetPluginOptionsRequest struct {
Options PluginOptions `json:"options"`
}
type PluginOptionsPositional struct {
PluginName string
UserId *Identifier
DomainId *Identifier
ServiceId *Identifier
Options PluginOptions
}
type TestPlugin interface {
PluginEnvName() []string
Version() PluginVersionInfo
AvailableOptions() PluginOptionsDocumentation
RunTest(options PluginOptions, meta map[string]string) (*PluginResult, error)
}
@ -52,6 +66,21 @@ type PluginAvailability struct {
LimitToServices []string `json:"limitToServices,omitempty"`
}
type PluginOptionsDocumentation struct {
RunOpts []PluginOptionDocumentation `json:"runOpts,omitempty"`
ServiceOpts []PluginOptionDocumentation `json:"serviceOpts,omitempty"`
DomainOpts []PluginOptionDocumentation `json:"domainOpts,omitempty"`
UserOpts []PluginOptionDocumentation `json:"userOpts,omitempty"`
AdminOpts []PluginOptionDocumentation `json:"adminOpts,omitempty"`
}
type PluginOptionDocumentation Field
type PluginStatus struct {
PluginVersionInfo
Opts PluginOptionsDocumentation `json:"options"`
}
type PluginResult struct {
Status PluginResultStatus `json:"status"`
StatusLine string `json:"statusLine,omitempty"`
@ -65,5 +94,8 @@ type PluginManager interface {
type TestPluginUsecase interface {
GetTestPlugin(string) (TestPlugin, error)
GetTestPluginOptions(string, *Identifier, *Identifier, *Identifier) (*PluginOptions, error)
ListTestPlugins() ([]TestPlugin, error)
OverwriteSomeTestPluginOptions(string, *Identifier, *Identifier, *Identifier, PluginOptions) error
SetTestPluginOptions(string, *Identifier, *Identifier, *Identifier, PluginOptions) error
}

View file

@ -31,6 +31,31 @@ func (p *MatrixTester) Version() happydns.PluginVersionInfo {
}
}
func (p *MatrixTester) AvailableOptions() happydns.PluginOptionsDocumentation {
return happydns.PluginOptionsDocumentation{
RunOpts: []happydns.PluginOptionDocumentation{
{
Id: "serviceDomain",
Type: "string",
Label: "Matrix domain",
Placeholder: "matrix.org",
Default: "matrix.org",
Required: true,
},
},
AdminOpts: []happydns.PluginOptionDocumentation{
{
Id: "federationTesterServer",
Type: "string",
Label: "Federation Tester Server",
Placeholder: "https://federationtester.matrix.org/",
Default: "https://federationtester.matrix.org/",
Required: true,
},
},
}
}
type FederationTesterResponse struct {
WellKnownResult struct {
Server string `json:"m.server"`