Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a command to test plugin compatibility with the krakend binary #851

Merged
merged 4 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cmd/krakend-ce/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ func main() {
})
}

commandsToLoad := []cmd.Command{
cmd.RunCommand,
cmd.CheckCommand,
cmd.PluginCommand,
cmd.VersionCommand,
cmd.AuditCommand,
krakend.NewTestPluginCmd(),
}

cmd.DefaultRoot = cmd.NewRoot(cmd.RootCommand, commandsToLoad...)
cmd.DefaultRoot.Cmd.CompletionOptions.DisableDefaultCmd = true

cmd.Execute(cfg, krakend.NewExecutor(ctx))
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/krakendio/krakend-viper/v2 v2.0.1
github.com/krakendio/krakend-xml/v2 v2.1.0
github.com/luraproject/lura/v2 v2.6.0
github.com/spf13/cobra v1.1.3
github.com/xeipuuv/gojsonschema v1.2.1-0.20200424115421-065759f9c3d7
golang.org/x/sync v0.6.0
)
Expand Down
190 changes: 190 additions & 0 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ package krakend

import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"

cmd "github.com/krakendio/krakend-cobra/v2"
"github.com/luraproject/lura/v2/logging"
proxy "github.com/luraproject/lura/v2/proxy/plugin"
client "github.com/luraproject/lura/v2/transport/http/client/plugin"
server "github.com/luraproject/lura/v2/transport/http/server/plugin"
"github.com/spf13/cobra"
)

// LoadPlugins loads and registers the plugins so they can be used if enabled at the configuration
Expand Down Expand Up @@ -73,3 +81,185 @@ func (pluginLoader) LoadWithContext(ctx context.Context, folder, pattern string,
type pluginLoaderErr interface {
Errs() []error
}

var (
serverExpected bool
clientExpected bool
modifierExpected bool

testPluginCmd = &cobra.Command{
Use: "test-plugin [flags] [artifacts]",
Short: "Tests that one or more plugins are loadable into KrakenD.",
Run: testPluginFunc,
Example: "krakend test-plugin -scm ./plugins/my_plugin.so ./plugins/my_other_plugin.so",
}

serverExpectedFlag cmd.FlagBuilder
clientExpectedFlag cmd.FlagBuilder
modifierExpectedFlag cmd.FlagBuilder

reLogErrorPlugins = regexp.MustCompile(`(?m)plugin \#\d+ \(.*\): (.*)`)
)

func init() {
serverExpectedFlag = cmd.BoolFlagBuilder(&serverExpected, "server", "s", false, "The artifact should contain a Server Plugin.")
clientExpectedFlag = cmd.BoolFlagBuilder(&clientExpected, "client", "c", false, "The artifact should contain a Client Plugin.")
modifierExpectedFlag = cmd.BoolFlagBuilder(&modifierExpected, "modifier", "m", false, "The artifact should contain a Req/Resp Modifier Plugin.")
}

func NewTestPluginCmd() cmd.Command {
return cmd.NewCommand(testPluginCmd, serverExpectedFlag, clientExpectedFlag, modifierExpectedFlag)
}

func testPluginFunc(ccmd *cobra.Command, args []string) {
if len(args) == 0 {
ccmd.Println("At least one plugin is required.")
os.Exit(1)
}
if !serverExpected && !clientExpected && !modifierExpected {
ccmd.Println("You must declare the expected type of the plugin.")
os.Exit(1)
}

start := time.Now()

ctx, cancel := context.WithCancel(context.Background())

var failed int
globalOK := true
for _, pluginPath := range args {
name := filepath.Base(pluginPath)
folder := filepath.Dir(pluginPath)
ok := true

if serverExpected {
ok = checkHandlerPlugin(ccmd, folder, name) && ok
}

if modifierExpected {
ok = checkModifierPlugin(ctx, ccmd, folder, name) && ok
}

if clientExpected {
ok = checkClientPlugin(ccmd, folder, name) && ok
}

if !ok {
failed++
}

globalOK = globalOK && ok
}

cancel()

if !globalOK {
ccmd.Println(fmt.Sprintf("[KO] %d tested plugin(s) in %s.\n%d plugin(s) failed.", len(args), time.Since(start), failed))
os.Exit(1)
}

ccmd.Println(fmt.Sprintf("[OK] %d tested plugin(s) in %s", len(args), time.Since(start)))
}

func checkClientPlugin(ccmd *cobra.Command, folder, name string) bool {
_, err := client.LoadWithLogger(
folder,
name,
client.RegisterClient,
logging.NoOp,
)
if err == nil {
ccmd.Println(fmt.Sprintf("[OK] CLIENT\t%s", name))
return true
}

var msg string
if mErrs, ok := err.(pluginLoaderErr); ok {
for _, err := range mErrs.Errs() {
msg += err.Error()
}
} else {
msg = err.Error()
}

if strings.Contains(msg, "symbol ClientRegisterer not found") {
ccmd.Println(fmt.Sprintf("[KO] CLIENT\t%s: The plugin does not contain a ClientRegisterer.", name))
return false
}

for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) {
msg = match[1]
}

ccmd.Println(fmt.Sprintf("[KO] CLIENT\t%s: %s", name, msg))
return false
}

func checkHandlerPlugin(ccmd *cobra.Command, folder, name string) bool {
_, err := server.LoadWithLogger(
folder,
name,
server.RegisterHandler,
logging.NoOp,
)
if err == nil {
ccmd.Println(fmt.Sprintf("[OK] SERVER\t%s", name))
return true
}

var msg string
if mErrs, ok := err.(pluginLoaderErr); ok {
for _, err := range mErrs.Errs() {
msg += err.Error()
}
} else {
msg = err.Error()
}

if strings.Contains(msg, "symbol HandlerRegisterer not found") {
ccmd.Println(fmt.Sprintf("[KO] SERVER\t%s: The plugin does not contain a HandlerRegisterer.", name))
return false
}

for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) {
msg = match[1]
}

ccmd.Println(fmt.Sprintf("[KO] SERVER\t%s: %s", name, msg))
return false
}

func checkModifierPlugin(ctx context.Context, ccmd *cobra.Command, folder, name string) bool {
_, err := proxy.LoadWithLoggerAndContext(
ctx,
folder,
name,
proxy.RegisterModifier,
logging.NoOp,
)
if err == nil {
ccmd.Println(fmt.Sprintf("[OK] MODIFIER\t%s", name))
return true
}

var msg string
if mErrs, ok := err.(pluginLoaderErr); ok {
for _, err := range mErrs.Errs() {
msg += err.Error()
}
} else {
msg = err.Error()
}

if strings.Contains(msg, "symbol ModifierRegisterer not found") {
ccmd.Println(fmt.Sprintf("[KO] MODIFIER\t%s: The plugin does not contain a ModifierRegisterer.", name))
return false
}

for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) {
msg = match[1]
}

ccmd.Println(fmt.Sprintf("[KO] MODIFIER\t%s: %s", name, msg))
return false
}