2021-05-11 17:14:10 +03:00
package plugins
import (
"context"
"fmt"
"net/http"
2023-11-30 23:42:06 +03:00
"os"
2021-05-11 17:14:10 +03:00
"path"
"reflect"
"strings"
"github.com/mitchellh/mapstructure"
2023-11-30 23:42:06 +03:00
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/logs"
2022-01-14 14:22:06 +03:00
"github.com/traefik/yaegi/interp"
2023-11-30 23:42:06 +03:00
"github.com/traefik/yaegi/stdlib"
2021-05-11 17:14:10 +03:00
)
2023-11-30 23:42:06 +03:00
type yaegiMiddlewareBuilder struct {
2021-05-11 17:14:10 +03:00
fnNew reflect . Value
2022-01-14 14:22:06 +03:00
fnCreateConfig reflect . Value
2021-05-11 17:14:10 +03:00
}
2023-11-30 23:42:06 +03:00
func newYaegiMiddlewareBuilder ( i * interp . Interpreter , basePkg , imp string ) ( * yaegiMiddlewareBuilder , error ) {
2021-05-11 17:14:10 +03:00
if basePkg == "" {
2022-01-14 14:22:06 +03:00
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 )
2021-05-11 17:14:10 +03:00
}
2022-01-14 14:22:06 +03:00
fnCreateConfig , err := i . Eval ( basePkg + ` .CreateConfig ` )
2021-05-11 17:14:10 +03:00
if err != nil {
return nil , fmt . Errorf ( "failed to eval CreateConfig: %w" , err )
}
2023-11-30 23:42:06 +03:00
return & yaegiMiddlewareBuilder {
2022-01-14 14:22:06 +03:00
fnNew : fnNew ,
fnCreateConfig : fnCreateConfig ,
} , nil
}
2023-11-30 23:42:06 +03:00
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 ) {
2022-01-14 14:22:06 +03:00
args := [ ] reflect . Value { reflect . ValueOf ( ctx ) , reflect . ValueOf ( next ) , cfg , reflect . ValueOf ( middlewareName ) }
2023-11-30 23:42:06 +03:00
results := b . fnNew . Call ( args )
2022-01-14 14:22:06 +03:00
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 ( ) )
}
2023-11-30 23:42:06 +03:00
2022-01-14 14:22:06 +03:00
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
}
2023-11-30 23:42:06 +03:00
func ( b yaegiMiddlewareBuilder ) createConfig ( config map [ string ] interface { } ) ( reflect . Value , error ) {
results := b . fnCreateConfig . Call ( nil )
2022-01-14 14:22:06 +03:00
if len ( results ) != 1 {
return reflect . Value { } , fmt . Errorf ( "invalid number of return for the CreateConfig function: %d" , len ( results ) )
}
vConfig := results [ 0 ]
2022-09-15 12:00:09 +03:00
if len ( config ) == 0 {
return vConfig , nil
}
2022-01-14 14:22:06 +03:00
2021-05-11 17:14:10 +03:00
cfg := & mapstructure . DecoderConfig {
2022-08-01 16:12:08 +03:00
DecodeHook : mapstructure . StringToSliceHookFunc ( "," ) ,
2021-05-11 17:14:10 +03:00
WeaklyTypedInput : true ,
Result : vConfig . Interface ( ) ,
}
decoder , err := mapstructure . NewDecoder ( cfg )
if err != nil {
2022-01-14 14:22:06 +03:00
return reflect . Value { } , fmt . Errorf ( "failed to create configuration decoder: %w" , err )
2021-05-11 17:14:10 +03:00
}
err = decoder . Decode ( config )
if err != nil {
2022-01-14 14:22:06 +03:00
return reflect . Value { } , fmt . Errorf ( "failed to decode configuration: %w" , err )
2021-05-11 17:14:10 +03:00
}
2022-01-14 14:22:06 +03:00
return vConfig , nil
}
2023-11-30 23:42:06 +03:00
// YaegiMiddleware is an HTTP handler plugin wrapper.
type YaegiMiddleware struct {
2022-01-14 14:22:06 +03:00
middlewareName string
config reflect . Value
2023-11-30 23:42:06 +03:00
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 )
2022-01-14 14:22:06 +03:00
}
2023-11-30 23:42:06 +03:00
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 )
2021-05-11 17:14:10 +03:00
if err != nil {
2023-11-30 23:42:06 +03:00
return nil , fmt . Errorf ( "failed to load symbols: %w" , err )
2021-05-11 17:14:10 +03:00
}
2023-11-30 23:42:06 +03:00
err = i . Use ( ppSymbols ( ) )
if err != nil {
return nil , fmt . Errorf ( "failed to load provider symbols: %w" , err )
}
2021-05-11 17:14:10 +03:00
2023-11-30 23:42:06 +03:00
_ , 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
2021-05-11 17:14:10 +03:00
}