Add plugin's support for provider
Co-authored-by: Julien Salleyron <julien@traefik.io>
This commit is contained in:
parent
de2437cfec
commit
63ef0f1cee
24 changed files with 928 additions and 116 deletions
6
Makefile
6
Makefile
|
@ -123,7 +123,7 @@ shell: build-dev-image
|
||||||
docs:
|
docs:
|
||||||
make -C ./docs docs
|
make -C ./docs docs
|
||||||
|
|
||||||
## Serve the documentation site localy
|
## Serve the documentation site locally
|
||||||
docs-serve:
|
docs-serve:
|
||||||
make -C ./docs docs-serve
|
make -C ./docs docs-serve
|
||||||
|
|
||||||
|
@ -135,6 +135,10 @@ docs-pull-images:
|
||||||
generate-crd:
|
generate-crd:
|
||||||
./script/update-generated-crd-code.sh
|
./script/update-generated-crd-code.sh
|
||||||
|
|
||||||
|
## Generate code from dynamic configuration https://github.com/traefik/genconf
|
||||||
|
generate-genconf:
|
||||||
|
go run ./cmd/internal/gen/
|
||||||
|
|
||||||
## Create packages for the release
|
## Create packages for the release
|
||||||
release-packages: generate-webui build-dev-image
|
release-packages: generate-webui build-dev-image
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
|
|
343
cmd/internal/gen/centrifuge.go
Normal file
343
cmd/internal/gen/centrifuge.go
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/importer"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/imports"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File a kind of AST element that represents a file.
|
||||||
|
type File struct {
|
||||||
|
Package string
|
||||||
|
Imports []string
|
||||||
|
Elements []Element
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element is a simplified version of a symbol.
|
||||||
|
type Element struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centrifuge a centrifuge.
|
||||||
|
// Generate Go Structures from Go structures.
|
||||||
|
type Centrifuge struct {
|
||||||
|
IncludedImports []string
|
||||||
|
ExcludedTypes []string
|
||||||
|
ExcludedFiles []string
|
||||||
|
|
||||||
|
TypeCleaner func(types.Type, string) string
|
||||||
|
PackageCleaner func(string) string
|
||||||
|
|
||||||
|
rootPkg string
|
||||||
|
fileSet *token.FileSet
|
||||||
|
pkg *types.Package
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCentrifuge creates a new Centrifuge.
|
||||||
|
func NewCentrifuge(rootPkg string) (*Centrifuge, error) {
|
||||||
|
fileSet := token.NewFileSet()
|
||||||
|
|
||||||
|
pkg, err := importer.ForCompiler(fileSet, "source", nil).Import(rootPkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Centrifuge{
|
||||||
|
fileSet: fileSet,
|
||||||
|
pkg: pkg,
|
||||||
|
rootPkg: rootPkg,
|
||||||
|
|
||||||
|
TypeCleaner: func(typ types.Type, _ string) string {
|
||||||
|
return typ.String()
|
||||||
|
},
|
||||||
|
PackageCleaner: func(s string) string {
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the code extraction and the code generation.
|
||||||
|
func (c Centrifuge) Run(dest string, pkgName string) error {
|
||||||
|
files, err := c.run(c.pkg.Scope(), c.rootPkg, pkgName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fileWriter{baseDir: dest}.Write(files)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range c.pkg.Imports() {
|
||||||
|
if contains(c.IncludedImports, p.Path()) {
|
||||||
|
fls, err := c.run(p.Scope(), p.Path(), p.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fileWriter{baseDir: filepath.Join(dest, p.Name())}.Write(fls)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Centrifuge) run(sc *types.Scope, rootPkg string, pkgName string) (map[string]*File, error) {
|
||||||
|
files := map[string]*File{}
|
||||||
|
|
||||||
|
for _, name := range sc.Names() {
|
||||||
|
if contains(c.ExcludedTypes, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
o := sc.Lookup(name)
|
||||||
|
if !o.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Base(c.fileSet.File(o.Pos()).Name())
|
||||||
|
if contains(c.ExcludedFiles, path.Join(rootPkg, filename)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, ok := files[filename]
|
||||||
|
if !ok {
|
||||||
|
files[filename] = &File{Package: pkgName}
|
||||||
|
fl = files[filename]
|
||||||
|
}
|
||||||
|
|
||||||
|
elt := Element{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ob := o.(type) {
|
||||||
|
case *types.TypeName:
|
||||||
|
|
||||||
|
switch obj := ob.Type().(*types.Named).Underlying().(type) {
|
||||||
|
case *types.Struct:
|
||||||
|
elt.Value = c.writeStruct(name, obj, rootPkg, fl)
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
elt.Value = fmt.Sprintf("type %s map[%s]%s\n", name, obj.Key().String(), c.TypeCleaner(obj.Elem(), rootPkg))
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
elt.Value = fmt.Sprintf("type %s []%v\n", name, c.TypeCleaner(obj.Elem(), rootPkg))
|
||||||
|
|
||||||
|
case *types.Basic:
|
||||||
|
elt.Value = fmt.Sprintf("type %s %v\n", name, obj.Name())
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("OTHER TYPE::: %s %T\n", name, o.Type().(*types.Named).Underlying())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Printf("OTHER::: %s %T\n", name, o)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(elt.Value) > 0 {
|
||||||
|
fl.Elements = append(fl.Elements, elt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Centrifuge) writeStruct(name string, obj *types.Struct, rootPkg string, elt *File) string {
|
||||||
|
b := strings.Builder{}
|
||||||
|
b.WriteString(fmt.Sprintf("type %s struct {\n", name))
|
||||||
|
|
||||||
|
for i := 0; i < obj.NumFields(); i++ {
|
||||||
|
field := obj.Field(i)
|
||||||
|
|
||||||
|
if !field.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fPkg := c.PackageCleaner(extractPackage(field.Type()))
|
||||||
|
if fPkg != "" && fPkg != rootPkg {
|
||||||
|
elt.Imports = append(elt.Imports, fPkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fType := c.TypeCleaner(field.Type(), rootPkg)
|
||||||
|
|
||||||
|
if field.Embedded() {
|
||||||
|
b.WriteString(fmt.Sprintf("\t%s\n", fType))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(fmt.Sprintf("\t%s %s", field.Name(), fType))
|
||||||
|
|
||||||
|
tags := obj.Tag(i)
|
||||||
|
if tags != "" {
|
||||||
|
tg := extractJSONTag(tags)
|
||||||
|
|
||||||
|
if tg != `json:"-"` {
|
||||||
|
b.WriteString(fmt.Sprintf(" `%s`", tg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("}\n")
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractJSONTag(value string) string {
|
||||||
|
fields := strings.Fields(value)
|
||||||
|
|
||||||
|
for _, field := range fields {
|
||||||
|
if strings.HasPrefix(field, `json:"`) {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractPackage(t types.Type) string {
|
||||||
|
switch tu := t.(type) {
|
||||||
|
case *types.Named:
|
||||||
|
return tu.Obj().Pkg().Path()
|
||||||
|
|
||||||
|
case *types.Slice:
|
||||||
|
if v, ok := tu.Elem().(*types.Named); ok {
|
||||||
|
return v.Obj().Pkg().Path()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
|
||||||
|
case *types.Map:
|
||||||
|
if v, ok := tu.Elem().(*types.Named); ok {
|
||||||
|
return v.Obj().Pkg().Path()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
|
||||||
|
case *types.Pointer:
|
||||||
|
return extractPackage(tu.Elem())
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(values []string, value string) bool {
|
||||||
|
for _, val := range values {
|
||||||
|
if val == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileWriter struct {
|
||||||
|
baseDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileWriter) Write(files map[string]*File) error {
|
||||||
|
err := os.MkdirAll(f.baseDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, file := range files {
|
||||||
|
err = f.writeFile(name, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileWriter) writeFile(name string, desc *File) error {
|
||||||
|
if len(desc.Elements) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(f.baseDir, name)
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
b := bytes.NewBufferString("package ")
|
||||||
|
b.WriteString(desc.Package)
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString("// Code generated by centrifuge. DO NOT EDIT.\n")
|
||||||
|
|
||||||
|
b.WriteString("\n")
|
||||||
|
f.writeImports(b, desc.Imports)
|
||||||
|
b.WriteString("\n")
|
||||||
|
|
||||||
|
for _, elt := range desc.Elements {
|
||||||
|
b.WriteString(elt.Value)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// gofmt
|
||||||
|
source, err := format.Source(b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Println(b.String())
|
||||||
|
return fmt.Errorf("failed to format sources: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// goimports
|
||||||
|
process, err := imports.Process(filename, source, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(string(source))
|
||||||
|
return fmt.Errorf("failed to format imports: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.Write(process)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fileWriter) writeImports(b io.StringWriter, imports []string) {
|
||||||
|
if len(imports) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq := map[string]struct{}{}
|
||||||
|
|
||||||
|
sort.Strings(imports)
|
||||||
|
|
||||||
|
_, _ = b.WriteString("import (\n")
|
||||||
|
for _, s := range imports {
|
||||||
|
if _, exist := uniq[s]; exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq[s] = struct{}{}
|
||||||
|
|
||||||
|
_, _ = b.WriteString(fmt.Sprintf(` "%s"`+"\n", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = b.WriteString(")\n")
|
||||||
|
}
|
124
cmd/internal/gen/main.go
Normal file
124
cmd/internal/gen/main.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"go/types"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootPkg = "github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
|
|
||||||
|
const (
|
||||||
|
destModuleName = "github.com/traefik/genconf"
|
||||||
|
destPkg = "dynamic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const marsh = `package %s
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type JSONPayload struct {
|
||||||
|
*Configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c JSONPayload) MarshalJSON() ([]byte, error) {
|
||||||
|
if c.Configuration == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(c.Configuration)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// main generate Go Structures from Go structures.
|
||||||
|
// Allows to create an external module (destModuleName) used by the plugin's providers
|
||||||
|
// that contains Go structs of the dynamic configuration and nothing else.
|
||||||
|
// These Go structs do not have any non-exported fields and do not rely on any external dependencies.
|
||||||
|
func main() {
|
||||||
|
dest := filepath.Join(path.Join(build.Default.GOPATH, "src"), destModuleName, destPkg)
|
||||||
|
|
||||||
|
log.Println("Output:", dest)
|
||||||
|
|
||||||
|
err := run(dest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(dest string) error {
|
||||||
|
centrifuge, err := NewCentrifuge(rootPkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
centrifuge.IncludedImports = []string{
|
||||||
|
"github.com/traefik/traefik/v2/pkg/tls",
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types",
|
||||||
|
}
|
||||||
|
|
||||||
|
centrifuge.ExcludedTypes = []string{
|
||||||
|
// tls
|
||||||
|
"CertificateStore", "Manager",
|
||||||
|
// dynamic
|
||||||
|
"Message", "Configurations",
|
||||||
|
// types
|
||||||
|
"HTTPCodeRanges", "HostResolverConfig",
|
||||||
|
}
|
||||||
|
|
||||||
|
centrifuge.ExcludedFiles = []string{
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types/logs.go",
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types/metrics.go",
|
||||||
|
}
|
||||||
|
|
||||||
|
centrifuge.TypeCleaner = cleanType
|
||||||
|
centrifuge.PackageCleaner = cleanPackage
|
||||||
|
|
||||||
|
err = centrifuge.Run(dest, destPkg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(filepath.Join(dest, "marshaler.go"), []byte(fmt.Sprintf(marsh, destPkg)), 0666)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanType(typ types.Type, base string) string {
|
||||||
|
if typ.String() == "github.com/traefik/traefik/v2/pkg/tls.FileOrContent" {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ.String() == "[]github.com/traefik/traefik/v2/pkg/tls.FileOrContent" {
|
||||||
|
return "[]string"
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ.String() == "github.com/traefik/paerser/types.Duration" {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(typ.String(), base) {
|
||||||
|
return strings.ReplaceAll(typ.String(), base+".", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(typ.String(), "github.com/traefik/traefik/v2/pkg/") {
|
||||||
|
return strings.ReplaceAll(typ.String(), "github.com/traefik/traefik/v2/pkg/", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return typ.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanPackage(src string) string {
|
||||||
|
switch src {
|
||||||
|
case "github.com/traefik/paerser/types":
|
||||||
|
return ""
|
||||||
|
case "github.com/traefik/traefik/v2/pkg/tls":
|
||||||
|
return path.Join(destModuleName, destPkg, "tls")
|
||||||
|
case "github.com/traefik/traefik/v2/pkg/types":
|
||||||
|
return path.Join(destModuleName, destPkg, "types")
|
||||||
|
default:
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -235,6 +236,20 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Providers plugins
|
||||||
|
|
||||||
|
for s, i := range staticConfiguration.Providers.Plugin {
|
||||||
|
p, err := pluginBuilder.BuildProvider(s, i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to build provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = providerAggregator.AddProvider(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("plugin: failed to add provider: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
|
|
||||||
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
|
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -84,6 +84,7 @@ require (
|
||||||
golang.org/x/mod v0.3.0
|
golang.org/x/mod v0.3.0
|
||||||
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
|
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
|
||||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858
|
||||||
google.golang.org/grpc v1.27.1
|
google.golang.org/grpc v1.27.1
|
||||||
gopkg.in/DataDog/dd-trace-go.v1 v1.19.0
|
gopkg.in/DataDog/dd-trace-go.v1 v1.19.0
|
||||||
gopkg.in/fsnotify.v1 v1.4.7
|
gopkg.in/fsnotify.v1 v1.4.7
|
||||||
|
|
|
@ -212,6 +212,10 @@ func getProviders(conf static.Configuration) []string {
|
||||||
if !field.IsNil() {
|
if !field.IsNil() {
|
||||||
providers = append(providers, v.Type().Field(i).Name)
|
providers = append(providers, v.Type().Field(i).Name)
|
||||||
}
|
}
|
||||||
|
} else if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(static.PluginConf{}) {
|
||||||
|
for _, value := range field.MapKeys() {
|
||||||
|
providers = append(providers, "plugin-"+value.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,9 @@ func TestHandler_Overview(t *testing.T) {
|
||||||
KubernetesCRD: &crd.Provider{},
|
KubernetesCRD: &crd.Provider{},
|
||||||
Rest: &rest.Provider{},
|
Rest: &rest.Provider{},
|
||||||
Rancher: &rancher.Provider{},
|
Rancher: &rancher.Provider{},
|
||||||
|
Plugin: map[string]static.PluginConf{
|
||||||
|
"test": map[string]interface{}{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
confDyn: runtime.Configuration{},
|
confDyn: runtime.Configuration{},
|
||||||
|
|
3
pkg/api/testdata/overview-providers.json
vendored
3
pkg/api/testdata/overview-providers.json
vendored
|
@ -28,7 +28,8 @@
|
||||||
"KubernetesIngress",
|
"KubernetesIngress",
|
||||||
"KubernetesCRD",
|
"KubernetesCRD",
|
||||||
"Rest",
|
"Rest",
|
||||||
"Rancher"
|
"Rancher",
|
||||||
|
"plugin-test"
|
||||||
],
|
],
|
||||||
"tcp": {
|
"tcp": {
|
||||||
"routers": {
|
"routers": {
|
||||||
|
|
4
pkg/config/static/plugins.go
Normal file
4
pkg/config/static/plugins.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package static
|
||||||
|
|
||||||
|
// PluginConf holds the plugin configuration.
|
||||||
|
type PluginConf map[string]interface{}
|
|
@ -190,6 +190,8 @@ type Providers struct {
|
||||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||||
|
|
||||||
|
Plugin map[string]PluginConf `description:"" json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||||
|
|
|
@ -4,11 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/traefik/yaegi/interp"
|
"github.com/traefik/yaegi/interp"
|
||||||
"github.com/traefik/yaegi/stdlib"
|
"github.com/traefik/yaegi/stdlib"
|
||||||
)
|
)
|
||||||
|
@ -34,13 +30,15 @@ type pluginContext struct {
|
||||||
|
|
||||||
// Builder is a plugin builder.
|
// Builder is a plugin builder.
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
descriptors map[string]pluginContext
|
middlewareDescriptors map[string]pluginContext
|
||||||
|
providerDescriptors map[string]pluginContext
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuilder creates a new Builder.
|
// NewBuilder creates a new Builder.
|
||||||
func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlugin) (*Builder, error) {
|
func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlugin) (*Builder, error) {
|
||||||
pb := &Builder{
|
pb := &Builder{
|
||||||
descriptors: map[string]pluginContext{},
|
middlewareDescriptors: map[string]pluginContext{},
|
||||||
|
providerDescriptors: map[string]pluginContext{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for pName, desc := range plugins {
|
for pName, desc := range plugins {
|
||||||
|
@ -52,17 +50,30 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlu
|
||||||
|
|
||||||
i := interp.New(interp.Options{GoPath: client.GoPath()})
|
i := interp.New(interp.Options{GoPath: client.GoPath()})
|
||||||
i.Use(stdlib.Symbols)
|
i.Use(stdlib.Symbols)
|
||||||
|
i.Use(ppSymbols())
|
||||||
|
|
||||||
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err)
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.descriptors[pName] = pluginContext{
|
switch manifest.Type {
|
||||||
interpreter: i,
|
case "middleware":
|
||||||
GoPath: client.GoPath(),
|
pb.middlewareDescriptors[pName] = pluginContext{
|
||||||
Import: manifest.Import,
|
interpreter: i,
|
||||||
BasePkg: manifest.BasePkg,
|
GoPath: client.GoPath(),
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
case "provider":
|
||||||
|
pb.providerDescriptors[pName] = pluginContext{
|
||||||
|
interpreter: i,
|
||||||
|
GoPath: client.GoPath(),
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,101 +85,32 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, devPlugin *DevPlu
|
||||||
|
|
||||||
i := interp.New(interp.Options{GoPath: devPlugin.GoPath})
|
i := interp.New(interp.Options{GoPath: devPlugin.GoPath})
|
||||||
i.Use(stdlib.Symbols)
|
i.Use(stdlib.Symbols)
|
||||||
|
i.Use(ppSymbols())
|
||||||
|
|
||||||
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", devPlugin.ModuleName, manifest.Import, err)
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", devPlugin.ModuleName, manifest.Import, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.descriptors[devPluginName] = pluginContext{
|
switch manifest.Type {
|
||||||
interpreter: i,
|
case "middleware":
|
||||||
GoPath: devPlugin.GoPath,
|
pb.middlewareDescriptors[devPluginName] = pluginContext{
|
||||||
Import: manifest.Import,
|
interpreter: i,
|
||||||
BasePkg: manifest.BasePkg,
|
GoPath: devPlugin.GoPath,
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
case "provider":
|
||||||
|
pb.providerDescriptors[devPluginName] = pluginContext{
|
||||||
|
interpreter: i,
|
||||||
|
GoPath: devPlugin.GoPath,
|
||||||
|
Import: manifest.Import,
|
||||||
|
BasePkg: manifest.BasePkg,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb, nil
|
return pb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build builds a plugin.
|
|
||||||
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) {
|
|
||||||
if b.descriptors == nil {
|
|
||||||
return nil, fmt.Errorf("plugin: no plugin definition in the static configuration: %s", pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptor, ok := b.descriptors[pName]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("plugin: unknown plugin type: %s", pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
m, err := newMiddleware(descriptor, config, middlewareName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.NewHandler, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware is a HTTP handler plugin wrapper.
|
|
||||||
type Middleware struct {
|
|
||||||
middlewareName string
|
|
||||||
fnNew reflect.Value
|
|
||||||
config reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMiddleware(descriptor pluginContext, config map[string]interface{}, middlewareName string) (*Middleware, error) {
|
|
||||||
basePkg := descriptor.BasePkg
|
|
||||||
if basePkg == "" {
|
|
||||||
basePkg = strings.ReplaceAll(path.Base(descriptor.Import), "-", "_")
|
|
||||||
}
|
|
||||||
|
|
||||||
vConfig, err := descriptor.interpreter.Eval(basePkg + `.CreateConfig()`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plugin: failed to eval CreateConfig: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
Result: vConfig.Interface(),
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plugin: failed to create configuration decoder: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plugin: failed to decode configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fnNew, err := descriptor.interpreter.Eval(basePkg + `.New`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plugin: failed to eval New: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Middleware{
|
|
||||||
middlewareName: middlewareName,
|
|
||||||
fnNew: fnNew,
|
|
||||||
config: vConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHandler creates a new HTTP handler.
|
|
||||||
func (m *Middleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
|
||||||
args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), m.config, reflect.ValueOf(m.middlewareName)}
|
|
||||||
results := m.fnNew.Call(args)
|
|
||||||
|
|
||||||
if len(results) > 1 && results[1].Interface() != nil {
|
|
||||||
return nil, results[1].Interface().(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler, ok := results[0].Interface().(http.Handler)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("plugin: invalid handler type: %T", results[0].Interface())
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler, nil
|
|
||||||
}
|
|
||||||
|
|
94
pkg/plugins/middlewares.go
Normal file
94
pkg/plugins/middlewares.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build builds a middleware plugin.
|
||||||
|
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) {
|
||||||
|
if b.middlewareDescriptors == nil {
|
||||||
|
return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor, ok := b.middlewareDescriptors[pName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown plugin type: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := newMiddleware(descriptor, config, middlewareName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.NewHandler, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware is a HTTP handler plugin wrapper.
|
||||||
|
type Middleware struct {
|
||||||
|
middlewareName string
|
||||||
|
fnNew reflect.Value
|
||||||
|
config reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMiddleware(descriptor pluginContext, config map[string]interface{}, middlewareName string) (*Middleware, error) {
|
||||||
|
basePkg := descriptor.BasePkg
|
||||||
|
if basePkg == "" {
|
||||||
|
basePkg = strings.ReplaceAll(path.Base(descriptor.Import), "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
vConfig, err := descriptor.interpreter.Eval(basePkg + `.CreateConfig()`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: vConfig.Interface(),
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := mapstructure.NewDecoder(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create configuration decoder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnNew, err := descriptor.interpreter.Eval(basePkg + `.New`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to eval New: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Middleware{
|
||||||
|
middlewareName: middlewareName,
|
||||||
|
fnNew: fnNew,
|
||||||
|
config: vConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new HTTP handler.
|
||||||
|
func (m *Middleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
||||||
|
args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), m.config, reflect.ValueOf(m.middlewareName)}
|
||||||
|
results := m.fnNew.Call(args)
|
||||||
|
|
||||||
|
if len(results) > 1 && results[1].Interface() != nil {
|
||||||
|
return nil, results[1].Interface().(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, ok := results[0].Interface().(http.Handler)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid handler type: %T", results[0].Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler, nil
|
||||||
|
}
|
|
@ -81,7 +81,10 @@ func checkDevPluginConfiguration(plugin *DevPlugin) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Type != "middleware" {
|
switch m.Type {
|
||||||
|
case "middleware", "provider":
|
||||||
|
// noop
|
||||||
|
default:
|
||||||
return errors.New("unsupported type")
|
return errors.New("unsupported type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
196
pkg/plugins/providers.go
Normal file
196
pkg/plugins/providers.go
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/safe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PP the interface of a plugin's provider.
|
||||||
|
type PP interface {
|
||||||
|
Init() error
|
||||||
|
Provide(cfgChan chan<- json.Marshaler) error
|
||||||
|
Stop() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type _PP struct {
|
||||||
|
WInit func() error
|
||||||
|
WProvide func(cfgChan chan<- json.Marshaler) error
|
||||||
|
WStop func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p _PP) Init() error {
|
||||||
|
return p.WInit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p _PP) Provide(cfgChan chan<- json.Marshaler) error {
|
||||||
|
return p.WProvide(cfgChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p _PP) Stop() error {
|
||||||
|
return p.WStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ppSymbols() map[string]map[string]reflect.Value {
|
||||||
|
return map[string]map[string]reflect.Value{
|
||||||
|
"github.com/traefik/traefik/v2/pkg/plugins": {
|
||||||
|
"PP": reflect.ValueOf((*PP)(nil)),
|
||||||
|
"_PP": reflect.ValueOf((*_PP)(nil)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildProvider builds a plugin's provider.
|
||||||
|
func (b Builder) BuildProvider(pName string, config map[string]interface{}) (provider.Provider, error) {
|
||||||
|
if b.providerDescriptors == nil {
|
||||||
|
return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor, ok := b.providerDescriptors[pName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown plugin type: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newProvider(descriptor, config, "plugin-"+pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider is a plugin's provider wrapper.
|
||||||
|
type Provider struct {
|
||||||
|
name string
|
||||||
|
pp PP
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProvider(descriptor pluginContext, config map[string]interface{}, providerName string) (*Provider, error) {
|
||||||
|
basePkg := descriptor.BasePkg
|
||||||
|
if basePkg == "" {
|
||||||
|
basePkg = strings.ReplaceAll(path.Base(descriptor.Import), "-", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
vConfig, err := descriptor.interpreter.Eval(basePkg + `.CreateConfig()`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: vConfig.Interface(),
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder, err := mapstructure.NewDecoder(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create configuration decoder: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = decoder.Decode(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = descriptor.interpreter.Eval(`package wrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
` + basePkg + ` "` + descriptor.Import + `"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewWrapper(ctx context.Context, config *` + basePkg + `.Config, name string) (plugins.PP, error) {
|
||||||
|
p, err := ` + basePkg + `.New(ctx, config, name)
|
||||||
|
var pv plugins.PP = p
|
||||||
|
return pv, err
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to eval wrapper: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnNew, err := descriptor.interpreter.Eval("wrapper.NewWrapper")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to eval New: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
args := []reflect.Value{reflect.ValueOf(ctx), vConfig, reflect.ValueOf(providerName)}
|
||||||
|
results := fnNew.Call(args)
|
||||||
|
|
||||||
|
if len(results) > 1 && results[1].Interface() != nil {
|
||||||
|
return nil, results[1].Interface().(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
prov, ok := results[0].Interface().(PP)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid provider type: %T", results[0].Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Provider{name: providerName, pp: prov}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init wraps the Init method of a plugin.
|
||||||
|
func (p *Provider) Init() error {
|
||||||
|
return p.pp.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide wraps the Provide method of a plugin.
|
||||||
|
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.WithoutContext().WithField(log.ProviderName, p.name).Errorf("panic inside the plugin %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
cfgChan := make(chan json.Marshaler)
|
||||||
|
|
||||||
|
err := p.pp.Provide(cfgChan)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error from %s: %w", p.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.GoCtx(func(ctx context.Context) {
|
||||||
|
logger := log.FromContext(log.With(ctx, log.Str(log.ProviderName, p.name)))
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := p.pp.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to stop the provider: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
case cfgPg := <-cfgChan:
|
||||||
|
marshalJSON, err := cfgPg.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to marshal configuration: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &dynamic.Configuration{}
|
||||||
|
err = json.Unmarshal(marshalJSON, cfg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to unmarshal configuration: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
configurationChan <- dynamic.Message{
|
||||||
|
ProviderName: p.name,
|
||||||
|
Configuration: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -347,12 +347,12 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
||||||
|
|
||||||
pluginType, rawPluginConfig, err := findPluginConfig(config.Plugin)
|
pluginType, rawPluginConfig, err := findPluginConfig(config.Plugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
plug, err := b.pluginBuilder.Build(pluginType, rawPluginConfig, middlewareName)
|
plug, err := b.pluginBuilder.Build(pluginType, rawPluginConfig, middlewareName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware = func(next http.Handler) (http.Handler, error) {
|
middleware = func(next http.Handler) (http.Handler, error) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ type PluginsBuilder interface {
|
||||||
|
|
||||||
func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]interface{}, error) {
|
func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[string]interface{}, error) {
|
||||||
if len(rawConfig) != 1 {
|
if len(rawConfig) != 1 {
|
||||||
return "", nil, errors.New("plugin: invalid configuration: no configuration or too many plugin definition")
|
return "", nil, errors.New("invalid configuration: no configuration or too many plugin definition")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginType string
|
var pluginType string
|
||||||
|
@ -27,11 +27,11 @@ func findPluginConfig(rawConfig map[string]dynamic.PluginConf) (string, map[stri
|
||||||
}
|
}
|
||||||
|
|
||||||
if pluginType == "" {
|
if pluginType == "" {
|
||||||
return "", nil, errors.New("plugin: missing plugin type")
|
return "", nil, errors.New("missing plugin type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rawPluginConfig) == 0 {
|
if len(rawPluginConfig) == 0 {
|
||||||
return "", nil, fmt.Errorf("plugin: missing plugin configuration: %s", pluginType)
|
return "", nil, fmt.Errorf("missing plugin configuration: %s", pluginType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pluginType, rawPluginConfig, nil
|
return pluginType, rawPluginConfig, nil
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<div class="text-subtitle2">PROVIDER</div>
|
<div class="text-subtitle2">PROVIDER</div>
|
||||||
<div class="block-right-text">
|
<div class="block-right-text">
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${middleware.provider}.svg`" />
|
<q-icon :name="`img:${getProviderLogoPath(middleware.provider)}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div class="block-right-text-label">{{middleware.provider}}</div>
|
<div class="block-right-text-label">{{middleware.provider}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1106,6 +1106,15 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return exData
|
return exData
|
||||||
|
},
|
||||||
|
getProviderLogoPath (provider) {
|
||||||
|
const name = provider.name.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 text-right">
|
<div class="col-3 text-right">
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${getProvider(service)}.svg`" />
|
<q-icon :name="`img:${getProviderLogoPath(service)}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,6 +61,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.data.provider
|
return this.data.provider
|
||||||
|
},
|
||||||
|
getProviderLogoPath (service) {
|
||||||
|
const provider = this.getProvider(service)
|
||||||
|
const name = provider.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="text-subtitle2">PROVIDER</div>
|
<div class="text-subtitle2">PROVIDER</div>
|
||||||
<div class="block-right-text">
|
<div class="block-right-text">
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${data.provider}.svg`" />
|
<q-icon :name="`img:${getProviderLogoPath}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div class="block-right-text-label">{{data.provider}}</div>
|
<div class="block-right-text-label">{{data.provider}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,6 +127,17 @@ export default {
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getProviderLogoPath () {
|
||||||
|
const name = this.data.provider.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div class="text-subtitle2">PROVIDER</div>
|
<div class="text-subtitle2">PROVIDER</div>
|
||||||
<div class="block-right-text">
|
<div class="block-right-text">
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${data.provider}.svg`" />
|
<q-icon :name="`img:${getProviderLogoPath}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div class="block-right-text-label">{{data.provider}}</div>
|
<div class="block-right-text-label">{{data.provider}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,6 +113,15 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
},
|
||||||
|
getProviderLogoPath () {
|
||||||
|
const name = this.data.provider.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<q-avatar>
|
<q-avatar>
|
||||||
<q-icon :name="`img:statics/providers/${getProvider(service)}.svg`" />
|
<q-icon :name="`img:${getProviderLogoPath(service)}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,6 +61,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.data.provider
|
return this.data.provider
|
||||||
|
},
|
||||||
|
getProviderLogoPath (service) {
|
||||||
|
const provider = this.getProvider(service)
|
||||||
|
const name = provider.name.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${name}.svg`" />
|
<q-icon :name="`img:${getLogoPath}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
props: ['name']
|
props: ['name'],
|
||||||
|
computed: {
|
||||||
|
getLogoPath () {
|
||||||
|
const name = this.name.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="row items-center no-wrap">
|
<div class="row items-center no-wrap">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<q-avatar class="provider-logo">
|
<q-avatar class="provider-logo">
|
||||||
<q-icon :name="`img:statics/providers/${getNameLogo}.svg`" />
|
<q-icon :name="`img:${getLogoPath}`" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,8 +25,14 @@ export default {
|
||||||
getName () {
|
getName () {
|
||||||
return this.name
|
return this.name
|
||||||
},
|
},
|
||||||
getNameLogo () {
|
getLogoPath () {
|
||||||
return this.getName.toLowerCase()
|
const name = this.getName.toLowerCase()
|
||||||
|
|
||||||
|
if (name.includes('plugin-')) {
|
||||||
|
return 'statics/providers/plugin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `statics/providers/${name}.svg`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
webui/src/statics/providers/plugin.svg
Normal file
10
webui/src/statics/providers/plugin.svg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>plugin</title>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="plugin" fill-rule="nonzero">
|
||||||
|
<circle id="Oval" fill="#6DC4A8" cx="16" cy="16" r="16"></circle>
|
||||||
|
<path d="M10.7517313,10.1738295 L22.2621133,19.1941905 L21.8304412,19.7449978 C20.0202381,22.0549461 17.2269562,23.0792113 14.836947,22.5465569 L14.5702202,22.8869226 C14.1903807,23.3716261 13.5380774,23.5005798 13.1206478,23.1734862 L12.1087414,22.3804984 L10.0368349,25.0243619 C9.65696684,25.5090655 9.00463498,25.6380191 8.58723393,25.3109255 L8.33426446,25.1126429 C7.9168634,24.7855208 7.88612346,24.1213154 8.26599153,23.6366119 L10.3379265,20.9927484 L9.32602009,20.199732 C8.90861904,19.8726385 8.87790764,19.2084045 9.25774716,18.723701 L9.43776279,18.4939934 C8.15541907,16.2757226 8.42328756,13.1450149 10.3200591,10.7246368 L10.7517313,10.1738295 Z M23.5543041,11.5531311 C24.1108484,11.9892558 24.1518349,12.8748916 23.6453537,13.5211725 L20.8654425,17.2106062 L18.8915999,15.6278127 L21.6215408,11.9351683 C22.1280221,11.2888874 22.9977598,11.1169778 23.5543041,11.5531311 Z M18.0521111,7.2411761 C18.6086553,7.67732938 18.6496134,8.56296514 18.1431321,9.20924605 L15.2398281,12.8891125 L13.3222617,11.3618714 L16.1193478,7.62324192 C16.6258005,6.97696101 17.4955668,6.80505137 18.0521111,7.2411761 Z" id="Shape" fill="#FFFFFF"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
Loading…
Reference in a new issue