Add http-wasm plugin support to Traefik
This commit is contained in:
parent
b2bb96390a
commit
6858dbdd07
9 changed files with 338 additions and 119 deletions
146
pkg/plugins/middlewareyaegi.go
Normal file
146
pkg/plugins/middlewareyaegi.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/yaegi/interp"
|
||||
"github.com/traefik/yaegi/stdlib"
|
||||
)
|
||||
|
||||
type yaegiMiddlewareBuilder struct {
|
||||
fnNew reflect.Value
|
||||
fnCreateConfig reflect.Value
|
||||
}
|
||||
|
||||
func newYaegiMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*yaegiMiddlewareBuilder, error) {
|
||||
if basePkg == "" {
|
||||
basePkg = strings.ReplaceAll(path.Base(imp), "-", "_")
|
||||
}
|
||||
|
||||
fnNew, err := i.Eval(basePkg + `.New`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to eval New: %w", err)
|
||||
}
|
||||
|
||||
fnCreateConfig, err := i.Eval(basePkg + `.CreateConfig`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
|
||||
}
|
||||
|
||||
return &yaegiMiddlewareBuilder{
|
||||
fnNew: fnNew,
|
||||
fnCreateConfig: fnCreateConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b yaegiMiddlewareBuilder) newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) {
|
||||
vConfig, err := b.createConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &YaegiMiddleware{
|
||||
middlewareName: middlewareName,
|
||||
config: vConfig,
|
||||
builder: b,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b yaegiMiddlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, error) {
|
||||
args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), cfg, reflect.ValueOf(middlewareName)}
|
||||
results := b.fnNew.Call(args)
|
||||
|
||||
if len(results) > 1 && results[1].Interface() != nil {
|
||||
err, ok := results[1].Interface().(error)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid error type: %T", results[0].Interface())
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler, ok := results[0].Interface().(http.Handler)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid handler type: %T", results[0].Interface())
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
func (b yaegiMiddlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) {
|
||||
results := b.fnCreateConfig.Call(nil)
|
||||
if len(results) != 1 {
|
||||
return reflect.Value{}, fmt.Errorf("invalid number of return for the CreateConfig function: %d", len(results))
|
||||
}
|
||||
|
||||
vConfig := results[0]
|
||||
if len(config) == 0 {
|
||||
return vConfig, nil
|
||||
}
|
||||
|
||||
cfg := &mapstructure.DecoderConfig{
|
||||
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
||||
WeaklyTypedInput: true,
|
||||
Result: vConfig.Interface(),
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(cfg)
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("failed to create configuration decoder: %w", err)
|
||||
}
|
||||
|
||||
err = decoder.Decode(config)
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("failed to decode configuration: %w", err)
|
||||
}
|
||||
|
||||
return vConfig, nil
|
||||
}
|
||||
|
||||
// YaegiMiddleware is an HTTP handler plugin wrapper.
|
||||
type YaegiMiddleware struct {
|
||||
middlewareName string
|
||||
config reflect.Value
|
||||
builder yaegiMiddlewareBuilder
|
||||
}
|
||||
|
||||
// NewHandler creates a new HTTP handler.
|
||||
func (m *YaegiMiddleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
||||
return m.builder.newHandler(ctx, next, m.config, m.middlewareName)
|
||||
}
|
||||
|
||||
func newInterpreter(ctx context.Context, goPath string, manifestImport string) (*interp.Interpreter, error) {
|
||||
i := interp.New(interp.Options{
|
||||
GoPath: goPath,
|
||||
Env: os.Environ(),
|
||||
Stdout: logs.NoLevel(*log.Ctx(ctx), zerolog.DebugLevel),
|
||||
Stderr: logs.NoLevel(*log.Ctx(ctx), zerolog.ErrorLevel),
|
||||
})
|
||||
|
||||
err := i.Use(stdlib.Symbols)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load symbols: %w", err)
|
||||
}
|
||||
|
||||
err = i.Use(ppSymbols())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load provider symbols: %w", err)
|
||||
}
|
||||
|
||||
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifestImport))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to import plugin code %q: %w", manifestImport, err)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue