Compare commits
No commits in common. "fe780c84de6673ec8626641cffdc865dbb5e3b41" and "f006e9bec1571d56d52320877da56687a6fa568f" have entirely different histories.
fe780c84de
...
f006e9bec1
11 changed files with 122 additions and 355 deletions
2
go.mod
2
go.mod
|
@ -32,7 +32,6 @@ require (
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.4
|
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f
|
github.com/hashicorp/nomad/api v0.0.0-20220506174431-b5665129cd1f
|
||||||
github.com/http-wasm/http-wasm-host-go v0.5.2
|
|
||||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0
|
github.com/influxdata/influxdb-client-go/v2 v2.7.0
|
||||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||||
github.com/instana/go-sensor v1.38.3
|
github.com/instana/go-sensor v1.38.3
|
||||||
|
@ -63,7 +62,6 @@ require (
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
||||||
github.com/tetratelabs/wazero v1.5.0
|
|
||||||
github.com/traefik/grpc-web v0.16.0
|
github.com/traefik/grpc-web v0.16.0
|
||||||
github.com/traefik/paerser v0.2.0
|
github.com/traefik/paerser v0.2.0
|
||||||
github.com/traefik/yaegi v0.15.1
|
github.com/traefik/yaegi v0.15.1
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1085,8 +1085,6 @@ github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09Uny
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/http-wasm/http-wasm-host-go v0.5.2 h1:5d/QgaaJtTF+qd0goBaxJJ7tcHP9n+gQUldJ7TsTexA=
|
|
||||||
github.com/http-wasm/http-wasm-host-go v0.5.2/go.mod h1:zQB3w+df4hryDEqBorGyA1DwPJ86LfKIASNLFuj6CuI=
|
|
||||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
@ -1809,8 +1807,6 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz2
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
|
||||||
github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
|
|
||||||
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
|
||||||
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
||||||
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
||||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/http-wasm/http-wasm-host-go/api"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// compile-time check to ensure ConsoleLogger implements api.Logger.
|
|
||||||
var _ api.Logger = WasmLogger{}
|
|
||||||
|
|
||||||
// WasmLogger is a convenience which writes anything above LogLevelInfo to os.Stdout.
|
|
||||||
type WasmLogger struct {
|
|
||||||
logger *zerolog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWasmLogger(logger *zerolog.Logger) *WasmLogger {
|
|
||||||
return &WasmLogger{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEnabled implements the same method as documented on api.Logger.
|
|
||||||
func (w WasmLogger) IsEnabled(level api.LogLevel) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log implements the same method as documented on api.Logger.
|
|
||||||
func (w WasmLogger) Log(_ context.Context, level api.LogLevel, message string) {
|
|
||||||
w.logger.WithLevel(zerolog.Level(level + 1)).Msg(message)
|
|
||||||
}
|
|
|
@ -4,34 +4,28 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"os"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/logs"
|
||||||
|
"github.com/traefik/yaegi/interp"
|
||||||
|
"github.com/traefik/yaegi/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constructor creates a plugin handler.
|
// Constructor creates a plugin handler.
|
||||||
type Constructor func(context.Context, http.Handler) (http.Handler, error)
|
type Constructor func(context.Context, http.Handler) (http.Handler, error)
|
||||||
|
|
||||||
type pluginMiddleware interface {
|
|
||||||
NewHandler(ctx context.Context, next http.Handler) (http.Handler, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type middlewareBuilder interface {
|
|
||||||
newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Builder is a plugin builder.
|
// Builder is a plugin builder.
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
|
middlewareBuilders map[string]*middlewareBuilder
|
||||||
providerBuilders map[string]providerBuilder
|
providerBuilders map[string]providerBuilder
|
||||||
middlewareBuilders map[string]middlewareBuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuilder creates a new Builder.
|
// NewBuilder creates a new Builder.
|
||||||
func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[string]LocalDescriptor) (*Builder, error) {
|
func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[string]LocalDescriptor) (*Builder, error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
pb := &Builder{
|
pb := &Builder{
|
||||||
middlewareBuilders: map[string]middlewareBuilder{},
|
middlewareBuilders: map[string]*middlewareBuilder{},
|
||||||
providerBuilders: map[string]providerBuilder{},
|
providerBuilders: map[string]providerBuilder{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,30 +36,44 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[
|
||||||
return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err)
|
return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.With().
|
logger := log.With().Str("plugin", "plugin-"+pName).Str("module", desc.ModuleName).Logger()
|
||||||
Str("plugin", "plugin-"+pName).
|
|
||||||
Str("module", desc.ModuleName).
|
i := interp.New(interp.Options{
|
||||||
Str("runtime", manifest.Runtime).
|
GoPath: client.GoPath(),
|
||||||
Logger()
|
Env: os.Environ(),
|
||||||
logCtx := logger.WithContext(ctx)
|
Stdout: logs.NoLevel(logger, zerolog.DebugLevel),
|
||||||
|
Stderr: logs.NoLevel(logger, zerolog.ErrorLevel),
|
||||||
|
})
|
||||||
|
|
||||||
|
err = i.Use(stdlib.Symbols)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to load symbols: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.Use(ppSymbols())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to load provider symbols: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err)
|
||||||
|
}
|
||||||
|
|
||||||
switch manifest.Type {
|
switch manifest.Type {
|
||||||
case typeMiddleware:
|
case "middleware":
|
||||||
middleware, err := newMiddlewareBuilder(logCtx, client.GoPath(), manifest, desc.ModuleName)
|
middleware, err := newMiddlewareBuilder(i, manifest.BasePkg, manifest.Import)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.middlewareBuilders[pName] = middleware
|
pb.middlewareBuilders[pName] = middleware
|
||||||
|
case "provider":
|
||||||
case typeProvider:
|
pb.providerBuilders[pName] = providerBuilder{
|
||||||
pBuilder, err := newProviderBuilder(logCtx, manifest, client.GoPath())
|
interpreter: i,
|
||||||
if err != nil {
|
Import: manifest.Import,
|
||||||
return nil, fmt.Errorf("%s: %w", desc.ModuleName, err)
|
BasePkg: manifest.BasePkg,
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.providerBuilders[pName] = pBuilder
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
||||||
}
|
}
|
||||||
|
@ -77,107 +85,48 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[
|
||||||
return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err)
|
return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.With().
|
logger := log.With().Str("plugin", "plugin-"+pName).Str("module", desc.ModuleName).Logger()
|
||||||
Str("plugin", "plugin-"+pName).
|
|
||||||
Str("module", desc.ModuleName).
|
i := interp.New(interp.Options{
|
||||||
Str("runtime", manifest.Runtime).
|
GoPath: localGoPath,
|
||||||
Logger()
|
Env: os.Environ(),
|
||||||
logCtx := logger.WithContext(ctx)
|
Stdout: logs.NoLevel(logger, zerolog.DebugLevel),
|
||||||
|
Stderr: logs.NoLevel(logger, zerolog.ErrorLevel),
|
||||||
|
})
|
||||||
|
|
||||||
|
err = i.Use(stdlib.Symbols)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to load symbols: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.Use(ppSymbols())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to load provider symbols: %w", desc.ModuleName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to import plugin code %q: %w", desc.ModuleName, manifest.Import, err)
|
||||||
|
}
|
||||||
|
|
||||||
switch manifest.Type {
|
switch manifest.Type {
|
||||||
case typeMiddleware:
|
case "middleware":
|
||||||
middleware, err := newMiddlewareBuilder(logCtx, localGoPath, manifest, desc.ModuleName)
|
middleware, err := newMiddlewareBuilder(i, manifest.BasePkg, manifest.Import)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.middlewareBuilders[pName] = middleware
|
pb.middlewareBuilders[pName] = middleware
|
||||||
|
case "provider":
|
||||||
case typeProvider:
|
pb.providerBuilders[pName] = providerBuilder{
|
||||||
builder, err := newProviderBuilder(logCtx, manifest, localGoPath)
|
interpreter: i,
|
||||||
if err != nil {
|
Import: manifest.Import,
|
||||||
return nil, fmt.Errorf("%s: %w", desc.ModuleName, err)
|
BasePkg: manifest.BasePkg,
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.providerBuilders[pName] = builder
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
return nil, fmt.Errorf("unknow plugin type: %s", manifest.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pb, nil
|
return pb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build builds a middleware plugin.
|
|
||||||
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) {
|
|
||||||
if b.middlewareBuilders == nil {
|
|
||||||
return nil, fmt.Errorf("no plugin definitions in the static configuration: %s", pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// plugin (pName) can be located in yaegi or wasm middleware builders.
|
|
||||||
if descriptor, ok := b.middlewareBuilders[pName]; ok {
|
|
||||||
m, err := descriptor.newMiddleware(config, middlewareName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.NewHandler, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown plugin type: %s", pName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMiddlewareBuilder(ctx context.Context, goPath string, manifest *Manifest, moduleName string) (middlewareBuilder, error) {
|
|
||||||
switch manifest.Runtime {
|
|
||||||
case runtimeWasm:
|
|
||||||
wasmPath, err := getWasmPath(manifest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("wasm path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newWasmMiddlewareBuilder(goPath, moduleName, wasmPath), nil
|
|
||||||
|
|
||||||
case runtimeYaegi, "":
|
|
||||||
i, err := newInterpreter(ctx, goPath, manifest.Import)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to craete Yaegi intepreter: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newYaegiMiddlewareBuilder(i, manifest.BasePkg, manifest.Import)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown plugin runtime: %s", manifest.Runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProviderBuilder(ctx context.Context, manifest *Manifest, goPath string) (providerBuilder, error) {
|
|
||||||
switch manifest.Runtime {
|
|
||||||
case runtimeYaegi, "":
|
|
||||||
i, err := newInterpreter(ctx, goPath, manifest.Import)
|
|
||||||
if err != nil {
|
|
||||||
return providerBuilder{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerBuilder{
|
|
||||||
interpreter: i,
|
|
||||||
Import: manifest.Import,
|
|
||||||
BasePkg: manifest.BasePkg,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return providerBuilder{}, fmt.Errorf("unknown plugin runtime: %s", manifest.Runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWasmPath(manifest *Manifest) (string, error) {
|
|
||||||
wasmPath := manifest.WasmPath
|
|
||||||
if wasmPath == "" {
|
|
||||||
wasmPath = "plugin.wasm"
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filepath.IsLocal(wasmPath) {
|
|
||||||
return "", fmt.Errorf("wasmPath must be a local path")
|
|
||||||
}
|
|
||||||
|
|
||||||
return wasmPath, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -279,15 +279,7 @@ func unzipFile(f *zipa.File, dest string) error {
|
||||||
defer func() { _ = rc.Close() }()
|
defer func() { _ = rc.Close() }()
|
||||||
|
|
||||||
pathParts := strings.SplitN(f.Name, "/", 2)
|
pathParts := strings.SplitN(f.Name, "/", 2)
|
||||||
|
p := filepath.Join(dest, pathParts[1])
|
||||||
var pp string
|
|
||||||
if len(pathParts) < 2 {
|
|
||||||
pp = pathParts[0]
|
|
||||||
} else {
|
|
||||||
pp = pathParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
p := filepath.Join(dest, pp)
|
|
||||||
|
|
||||||
if f.FileInfo().IsDir() {
|
if f.FileInfo().IsDir() {
|
||||||
err = os.MkdirAll(p, f.Mode())
|
err = os.MkdirAll(p, f.Mode())
|
||||||
|
|
|
@ -4,25 +4,39 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"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/interp"
|
||||||
"github.com/traefik/yaegi/stdlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type yaegiMiddlewareBuilder struct {
|
// Build builds a middleware plugin.
|
||||||
|
func (b Builder) Build(pName string, config map[string]interface{}, middlewareName string) (Constructor, error) {
|
||||||
|
if b.middlewareBuilders == nil {
|
||||||
|
return nil, fmt.Errorf("no plugin definition in the static configuration: %s", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor, ok := b.middlewareBuilders[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
|
||||||
|
}
|
||||||
|
|
||||||
|
type middlewareBuilder struct {
|
||||||
fnNew reflect.Value
|
fnNew reflect.Value
|
||||||
fnCreateConfig reflect.Value
|
fnCreateConfig reflect.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func newYaegiMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*yaegiMiddlewareBuilder, error) {
|
func newMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*middlewareBuilder, error) {
|
||||||
if basePkg == "" {
|
if basePkg == "" {
|
||||||
basePkg = strings.ReplaceAll(path.Base(imp), "-", "_")
|
basePkg = strings.ReplaceAll(path.Base(imp), "-", "_")
|
||||||
}
|
}
|
||||||
|
@ -37,35 +51,21 @@ func newYaegiMiddlewareBuilder(i *interp.Interpreter, basePkg, imp string) (*yae
|
||||||
return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
|
return nil, fmt.Errorf("failed to eval CreateConfig: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &yaegiMiddlewareBuilder{
|
return &middlewareBuilder{
|
||||||
fnNew: fnNew,
|
fnNew: fnNew,
|
||||||
fnCreateConfig: fnCreateConfig,
|
fnCreateConfig: fnCreateConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b yaegiMiddlewareBuilder) newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) {
|
func (p middlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, 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)}
|
args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(next), cfg, reflect.ValueOf(middlewareName)}
|
||||||
results := b.fnNew.Call(args)
|
results := p.fnNew.Call(args)
|
||||||
|
|
||||||
if len(results) > 1 && results[1].Interface() != nil {
|
if len(results) > 1 && results[1].Interface() != nil {
|
||||||
err, ok := results[1].Interface().(error)
|
err, ok := results[1].Interface().(error)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("invalid error type: %T", results[0].Interface())
|
return nil, fmt.Errorf("invalid error type: %T", results[0].Interface())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ func (b yaegiMiddlewareBuilder) newHandler(ctx context.Context, next http.Handle
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b yaegiMiddlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) {
|
func (p middlewareBuilder) createConfig(config map[string]interface{}) (reflect.Value, error) {
|
||||||
results := b.fnCreateConfig.Call(nil)
|
results := p.fnCreateConfig.Call(nil)
|
||||||
if len(results) != 1 {
|
if len(results) != 1 {
|
||||||
return reflect.Value{}, fmt.Errorf("invalid number of return for the CreateConfig function: %d", len(results))
|
return reflect.Value{}, fmt.Errorf("invalid number of return for the CreateConfig function: %d", len(results))
|
||||||
}
|
}
|
||||||
|
@ -107,40 +107,27 @@ func (b yaegiMiddlewareBuilder) createConfig(config map[string]interface{}) (ref
|
||||||
return vConfig, nil
|
return vConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// YaegiMiddleware is an HTTP handler plugin wrapper.
|
// Middleware is an HTTP handler plugin wrapper.
|
||||||
type YaegiMiddleware struct {
|
type Middleware struct {
|
||||||
middlewareName string
|
middlewareName string
|
||||||
config reflect.Value
|
config reflect.Value
|
||||||
builder yaegiMiddlewareBuilder
|
builder *middlewareBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMiddleware(builder *middlewareBuilder, config map[string]interface{}, middlewareName string) (*Middleware, error) {
|
||||||
|
vConfig, err := builder.createConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Middleware{
|
||||||
|
middlewareName: middlewareName,
|
||||||
|
config: vConfig,
|
||||||
|
builder: builder,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a new HTTP handler.
|
// NewHandler creates a new HTTP handler.
|
||||||
func (m *YaegiMiddleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
func (m *Middleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
||||||
return m.builder.newHandler(ctx, next, m.config, m.middlewareName)
|
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
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/http-wasm/http-wasm-host-go/handler"
|
|
||||||
wasm "github.com/http-wasm/http-wasm-host-go/handler/nethttp"
|
|
||||||
"github.com/tetratelabs/wazero"
|
|
||||||
"github.com/traefik/traefik/v3/pkg/logs"
|
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
|
||||||
)
|
|
||||||
|
|
||||||
type wasmMiddlewareBuilder struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWasmMiddlewareBuilder(goPath string, moduleName, wasmPath string) *wasmMiddlewareBuilder {
|
|
||||||
return &wasmMiddlewareBuilder{path: filepath.Join(goPath, "src", moduleName, wasmPath)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b wasmMiddlewareBuilder) newMiddleware(config map[string]interface{}, middlewareName string) (pluginMiddleware, error) {
|
|
||||||
return &WasmMiddleware{
|
|
||||||
middlewareName: middlewareName,
|
|
||||||
config: reflect.ValueOf(config),
|
|
||||||
builder: b,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b wasmMiddlewareBuilder) newHandler(ctx context.Context, next http.Handler, cfg reflect.Value, middlewareName string) (http.Handler, error) {
|
|
||||||
code, err := os.ReadFile(b.path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("loading Wasm binary: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := middlewares.GetLogger(ctx, middlewareName, "wasm")
|
|
||||||
|
|
||||||
opts := []handler.Option{
|
|
||||||
handler.ModuleConfig(wazero.NewModuleConfig().WithSysWalltime()),
|
|
||||||
handler.Logger(logs.NewWasmLogger(logger)),
|
|
||||||
}
|
|
||||||
|
|
||||||
i := cfg.Interface()
|
|
||||||
if i != nil {
|
|
||||||
config, ok := i.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("could not type assert config: %T", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("marshaling config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = append(opts, handler.GuestConfig(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
mw, err := wasm.NewMiddleware(context.Background(), code, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mw.NewHandler(ctx, next), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WasmMiddleware is an HTTP handler plugin wrapper.
|
|
||||||
type WasmMiddleware struct {
|
|
||||||
middlewareName string
|
|
||||||
config reflect.Value
|
|
||||||
builder wasmMiddlewareBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHandler creates a new HTTP handler.
|
|
||||||
func (m WasmMiddleware) NewHandler(ctx context.Context, next http.Handler) (http.Handler, error) {
|
|
||||||
return m.builder.newHandler(ctx, next, m.config, m.middlewareName)
|
|
||||||
}
|
|
|
@ -138,28 +138,18 @@ func checkLocalPluginManifest(descriptor LocalDescriptor) error {
|
||||||
var errs *multierror.Error
|
var errs *multierror.Error
|
||||||
|
|
||||||
switch m.Type {
|
switch m.Type {
|
||||||
case typeMiddleware:
|
case "middleware", "provider":
|
||||||
if m.Runtime != runtimeYaegi && m.Runtime != runtimeWasm && m.Runtime != "" {
|
// noop
|
||||||
errs = multierror.Append(errs, fmt.Errorf("%s: unsupported runtime '%q'", descriptor.ModuleName, m.Runtime))
|
|
||||||
}
|
|
||||||
|
|
||||||
case typeProvider:
|
|
||||||
if m.Runtime != runtimeYaegi && m.Runtime != "" {
|
|
||||||
errs = multierror.Append(errs, fmt.Errorf("%s: unsupported runtime '%q'", descriptor.ModuleName, m.Runtime))
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errs = multierror.Append(errs, fmt.Errorf("%s: unsupported type %q", descriptor.ModuleName, m.Type))
|
errs = multierror.Append(errs, fmt.Errorf("%s: unsupported type %q", descriptor.ModuleName, m.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.IsYaegiPlugin() {
|
if m.Import == "" {
|
||||||
if m.Import == "" {
|
errs = multierror.Append(errs, fmt.Errorf("%s: missing import", descriptor.ModuleName))
|
||||||
errs = multierror.Append(errs, fmt.Errorf("%s: missing import", descriptor.ModuleName))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(m.Import, descriptor.ModuleName) {
|
if !strings.HasPrefix(m.Import, descriptor.ModuleName) {
|
||||||
errs = multierror.Append(errs, fmt.Errorf("the import %q must be related to the module name %q", m.Import, descriptor.ModuleName))
|
errs = multierror.Append(errs, fmt.Errorf("the import %q must be related to the module name %q", m.Import, descriptor.ModuleName))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.DisplayName == "" {
|
if m.DisplayName == "" {
|
||||||
|
|
|
@ -1,15 +1,5 @@
|
||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
const (
|
|
||||||
runtimeYaegi = "yaegi"
|
|
||||||
runtimeWasm = "wasm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
typeMiddleware = "middleware"
|
|
||||||
typeProvider = "provider"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Descriptor The static part of a plugin configuration.
|
// Descriptor The static part of a plugin configuration.
|
||||||
type Descriptor struct {
|
type Descriptor struct {
|
||||||
// ModuleName (required)
|
// ModuleName (required)
|
||||||
|
@ -29,17 +19,9 @@ type LocalDescriptor struct {
|
||||||
type Manifest struct {
|
type Manifest struct {
|
||||||
DisplayName string `yaml:"displayName"`
|
DisplayName string `yaml:"displayName"`
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Runtime string `yaml:"runtime"`
|
|
||||||
WasmPath string `yaml:"wasmPath"`
|
|
||||||
Import string `yaml:"import"`
|
Import string `yaml:"import"`
|
||||||
BasePkg string `yaml:"basePkg"`
|
BasePkg string `yaml:"basePkg"`
|
||||||
Compatibility string `yaml:"compatibility"`
|
Compatibility string `yaml:"compatibility"`
|
||||||
Summary string `yaml:"summary"`
|
Summary string `yaml:"summary"`
|
||||||
TestData map[string]interface{} `yaml:"testData"`
|
TestData map[string]interface{} `yaml:"testData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsYaegiPlugin returns true if the plugin is a Yaegi plugin.
|
|
||||||
func (m *Manifest) IsYaegiPlugin() bool {
|
|
||||||
// defaults always Yaegi to have backwards compatibility to plugins without runtime
|
|
||||||
return m.Runtime == runtimeYaegi || m.Runtime == ""
|
|
||||||
}
|
|
||||||
|
|
|
@ -266,8 +266,9 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rule := "HostSNI(`" + sniHost + "`)"
|
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
|
||||||
if err := r.muxerHTTPS.AddRoute(rule, tcpmuxer.GetRulePriority(rule), tcpHandler); err != nil {
|
// so there's no need for specifying a priority for them.
|
||||||
|
if err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler); err != nil {
|
||||||
log.Error().Err(err).Msg("Error while adding route for host")
|
log.Error().Err(err).Msg("Error while adding route for host")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -494,21 +494,6 @@ func Test_Routing(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "HTTPS router && HTTPS CatchAll router",
|
|
||||||
routers: []applyRouter{routerHTTPS, routerHTTPSPathPrefix},
|
|
||||||
checks: []checkCase{
|
|
||||||
{
|
|
||||||
desc: "HTTPS TLS 1.0 request should fail",
|
|
||||||
checkRouter: checkHTTPSTLS10,
|
|
||||||
expectedError: "wrong TLS version",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "HTTPS TLS 1.2 request should be handled by HTTPS service",
|
|
||||||
checkRouter: checkHTTPSTLS12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "All routers, all checks",
|
desc: "All routers, all checks",
|
||||||
routers: []applyRouter{routerTCPCatchAll, routerHTTP, routerHTTPS, routerTCPTLS, routerTCPTLSCatchAll},
|
routers: []applyRouter{routerTCPCatchAll, routerHTTP, routerHTTPS, routerTCPTLS, routerTCPTLSCatchAll},
|
||||||
|
|
Loading…
Reference in a new issue