mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-28 03:30:01 +08:00
refactor: plugin functions can be defined in any format
This commit is contained in:
@@ -1,55 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
hclog "github.com/hashicorp/go-hclog"
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Here is a real implementation of funcCaller
|
|
||||||
type functionPlugin struct {
|
|
||||||
logger hclog.Logger
|
|
||||||
functions map[string]func(args ...interface{}) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *functionPlugin) GetNames() ([]string, error) {
|
|
||||||
var names []string
|
|
||||||
for name := range p.functions {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return names, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
|
|
||||||
p.logger.Info("Call function", "funcName", funcName, "args", args)
|
|
||||||
|
|
||||||
f, ok := p.functions[funcName]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("function %s not found", funcName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return f(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var functions = make(map[string]func(args ...interface{}) (interface{}, error))
|
|
||||||
|
|
||||||
func Register(funcName string, fn func(args ...interface{}) (interface{}, error)) {
|
|
||||||
functions[funcName] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func Serve() {
|
|
||||||
funcPlugin := &functionPlugin{
|
|
||||||
logger: logger,
|
|
||||||
functions: functions,
|
|
||||||
}
|
|
||||||
// pluginMap is the map of plugins we can dispense.
|
|
||||||
var pluginMap = map[string]plugin.Plugin{
|
|
||||||
Name: &hashicorpPlugin{Impl: funcPlugin},
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.Serve(&plugin.ServeConfig{
|
|
||||||
HandshakeConfig: handshakeConfig,
|
|
||||||
Plugins: pluginMap,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
fmt.Println("[TestMain] build go plugin")
|
|
||||||
cmd := exec.Command("go", "build", "-o=debugtalk", "plugin/debugtalk.go")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInitHashicorpPlugin(t *testing.T) {
|
|
||||||
f, err := Init("./debugtalk")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer Quit()
|
|
||||||
|
|
||||||
v, err := f.Call("sum_int", 1, 2, 3, 4)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, 10, v) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
v, err = f.Call("concatenate_string", "a", "b", "c")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, "abc", v) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package plugin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
hclog "github.com/hashicorp/go-hclog"
|
|
||||||
"github.com/hashicorp/go-plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const Name = "debugtalk"
|
|
||||||
|
|
||||||
// handshakeConfigs are used to just do a basic handshake between
|
|
||||||
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
|
||||||
// This prevents users from executing bad plugins or executing a plugin
|
|
||||||
// directory. It is a UX feature, not a security feature.
|
|
||||||
var handshakeConfig = plugin.HandshakeConfig{
|
|
||||||
ProtocolVersion: 1,
|
|
||||||
MagicCookieKey: "HttpRunnerPlus",
|
|
||||||
MagicCookieValue: Name,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an hclog.Logger
|
|
||||||
var logger = hclog.New(&hclog.LoggerOptions{
|
|
||||||
Name: Name,
|
|
||||||
Output: os.Stdout,
|
|
||||||
Level: hclog.Debug,
|
|
||||||
})
|
|
||||||
|
|
||||||
var client *plugin.Client
|
|
||||||
|
|
||||||
func Init(path string) (FuncCaller, error) {
|
|
||||||
// launch the plugin process
|
|
||||||
client = plugin.NewClient(&plugin.ClientConfig{
|
|
||||||
HandshakeConfig: handshakeConfig,
|
|
||||||
Plugins: map[string]plugin.Plugin{
|
|
||||||
Name: &hashicorpPlugin{},
|
|
||||||
},
|
|
||||||
Cmd: exec.Command(path),
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Connect via RPC
|
|
||||||
rpcClient, err := client.Client()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request the plugin
|
|
||||||
raw, err := rpcClient.Dispense(Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should have a Function now! This feels like a normal interface
|
|
||||||
// implementation but is in fact over an RPC connection.
|
|
||||||
function := raw.(FuncCaller)
|
|
||||||
return function, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Quit() {
|
|
||||||
client.Kill()
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
plugin "github.com/httprunner/hrp/plugin-gosdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SumInt(args ...interface{}) (interface{}, error) {
|
|
||||||
var sum int
|
|
||||||
for _, arg := range args {
|
|
||||||
v, ok := arg.(int)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
|
||||||
}
|
|
||||||
sum += v
|
|
||||||
}
|
|
||||||
return sum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConcatenateString(args ...interface{}) (interface{}, error) {
|
|
||||||
var result string
|
|
||||||
for _, arg := range args {
|
|
||||||
v, ok := arg.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
|
||||||
}
|
|
||||||
result += v
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// register functions and build to plugin binary
|
|
||||||
func main() {
|
|
||||||
plugin.Register("sum_int", SumInt)
|
|
||||||
plugin.Register("concatenate_string", ConcatenateString)
|
|
||||||
plugin.Serve()
|
|
||||||
}
|
|
||||||
@@ -12,15 +12,16 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/hrp/internal/builtin"
|
"github.com/httprunner/hrp/internal/builtin"
|
||||||
"github.com/httprunner/hrp/internal/ga"
|
"github.com/httprunner/hrp/internal/ga"
|
||||||
pluginSDK "github.com/httprunner/hrp/plugin-gosdk"
|
pluginHost "github.com/httprunner/hrp/plugin/host"
|
||||||
|
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pluginFile string
|
type pluginFile string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goPluginFile pluginFile = pluginSDK.Name + ".so" // built from go plugin
|
goPluginFile pluginFile = pluginShared.Name + ".so" // built from go plugin
|
||||||
hashicorpGoPluginFile pluginFile = pluginSDK.Name + ".bin" // built from hashicorp go plugin
|
hashicorpGoPluginFile pluginFile = pluginShared.Name + ".bin" // built from hashicorp go plugin
|
||||||
hashicorpPyPluginFile pluginFile = pluginSDK.Name + ".py"
|
hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hrpPlugin interface {
|
type hrpPlugin interface {
|
||||||
@@ -91,7 +92,7 @@ func (p *goPlugin) has(funcName string) bool {
|
|||||||
|
|
||||||
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||||
fn := p.cachedFunctions[funcName]
|
fn := p.cachedFunctions[funcName]
|
||||||
return callFunc(fn, args...)
|
return pluginShared.CallFunc(fn, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *goPlugin) quit() error {
|
func (p *goPlugin) quit() error {
|
||||||
@@ -101,13 +102,13 @@ func (p *goPlugin) quit() error {
|
|||||||
|
|
||||||
// hashicorpPlugin implements hashicorp/go-plugin
|
// hashicorpPlugin implements hashicorp/go-plugin
|
||||||
type hashicorpPlugin struct {
|
type hashicorpPlugin struct {
|
||||||
pluginSDK.FuncCaller
|
pluginShared.FuncCaller
|
||||||
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *hashicorpPlugin) init(path string) error {
|
func (p *hashicorpPlugin) init(path string) error {
|
||||||
|
|
||||||
f, err := pluginSDK.Init(path)
|
f, err := pluginHost.Init(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed")
|
log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed")
|
||||||
return err
|
return err
|
||||||
@@ -147,7 +148,7 @@ func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{
|
|||||||
|
|
||||||
func (p *hashicorpPlugin) quit() error {
|
func (p *hashicorpPlugin) quit() error {
|
||||||
// kill hashicorp plugin process
|
// kill hashicorp plugin process
|
||||||
pluginSDK.Quit()
|
pluginHost.Quit()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,49 +231,5 @@ func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{
|
|||||||
fn := reflect.ValueOf(function)
|
fn := reflect.ValueOf(function)
|
||||||
|
|
||||||
// call with builtin function
|
// call with builtin function
|
||||||
return callFunc(fn, arguments...)
|
return pluginShared.CallFunc(fn, arguments...)
|
||||||
}
|
|
||||||
|
|
||||||
// callFunc calls function with arguments
|
|
||||||
// it is used when calling go plugin or builtin functions
|
|
||||||
func callFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
|
||||||
fnArgsNum := fn.Type().NumIn()
|
|
||||||
if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice {
|
|
||||||
// last argument is slice, do not check arguments number
|
|
||||||
// e.g. ...interface{}
|
|
||||||
// e.g. a, b string, c ...interface{}
|
|
||||||
} else if fnArgsNum != len(args) {
|
|
||||||
// function arguments not match
|
|
||||||
return nil, fmt.Errorf("function arguments number not match")
|
|
||||||
}
|
|
||||||
// arguments do not have slice, and arguments number matched
|
|
||||||
|
|
||||||
argumentsValue := make([]reflect.Value, len(args))
|
|
||||||
for index, argument := range args {
|
|
||||||
if argument == nil {
|
|
||||||
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
|
|
||||||
} else {
|
|
||||||
argumentsValue[index] = reflect.ValueOf(args[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resultValues := fn.Call(argumentsValue)
|
|
||||||
if resultValues == nil {
|
|
||||||
// no returns
|
|
||||||
return nil, nil
|
|
||||||
} else if len(resultValues) == 2 {
|
|
||||||
// return two arguments: interface{}, error
|
|
||||||
if resultValues[1].Interface() != nil {
|
|
||||||
return resultValues[0].Interface(), resultValues[1].Interface().(error)
|
|
||||||
} else {
|
|
||||||
return resultValues[0].Interface(), nil
|
|
||||||
}
|
|
||||||
} else if len(resultValues) == 1 {
|
|
||||||
// return one arguments: interface{}
|
|
||||||
return resultValues[0].Interface(), nil
|
|
||||||
} else {
|
|
||||||
// return more than 2 arguments, unexpected
|
|
||||||
err := fmt.Errorf("function should return at most 2 arguments")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
plugin "github.com/httprunner/hrp/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SumTwoInt(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumInts(args ...int) int {
|
||||||
|
var sum int
|
||||||
|
for _, arg := range args {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sum(args ...interface{}) (interface{}, error) {
|
||||||
|
var sum float64
|
||||||
|
for _, arg := range args {
|
||||||
|
switch v := arg.(type) {
|
||||||
|
case int:
|
||||||
|
sum += float64(v)
|
||||||
|
case float64:
|
||||||
|
sum += v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumTwoString(a, b string) string {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func SumStrings(s ...string) string {
|
||||||
|
var sum string
|
||||||
|
for _, arg := range s {
|
||||||
|
sum += arg
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func Concatenate(args ...interface{}) (interface{}, error) {
|
||||||
|
var result string
|
||||||
|
for _, arg := range args {
|
||||||
|
result += fmt.Sprintf("%v", arg)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register functions and build to plugin binary
|
||||||
|
func main() {
|
||||||
|
plugin.Register("sum_ints", SumInts)
|
||||||
|
plugin.Register("sum_two_int", SumTwoInt)
|
||||||
|
plugin.Register("sum", Sum)
|
||||||
|
plugin.Register("sum_two_string", SumTwoString)
|
||||||
|
plugin.Register("sum_strings", SumStrings)
|
||||||
|
plugin.Register("concatenate", Concatenate)
|
||||||
|
plugin.Serve()
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/plugin/host"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
fmt.Println("[TestMain] build go plugin")
|
||||||
|
cmd := exec.Command("go", "build", "-o=debugtalk.bin", "./debugtalk.go")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitHashicorpPlugin(t *testing.T) {
|
||||||
|
f, err := host.Init("./debugtalk.bin")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer host.Quit()
|
||||||
|
|
||||||
|
v1, err := f.GetNames()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, v1, "sum_ints") {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, v1, "concatenate") {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2 interface{}
|
||||||
|
v2, err = f.Call("sum_ints", 1, 2, 3, 4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, 10, v2) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
v2, err = f.Call("sum_two_int", 1, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, 3, v2) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
v2, err = f.Call("sum", 1, 2, 3.4, 5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, 11.4, v2) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
var v3 interface{}
|
||||||
|
v3, err = f.Call("sum_two_string", "a", "b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "ab", v3) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
v3, err = f.Call("sum_strings", "a", "b", "c")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "abc", v3) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
v3, err = f.Call("concatenate", "a", 2, "c", 3.4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, "a2c3.4", v3) {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
hclog "github.com/hashicorp/go-hclog"
|
||||||
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
|
"github.com/httprunner/hrp/plugin/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
var client *plugin.Client
|
||||||
|
|
||||||
|
func Init(path string) (shared.FuncCaller, error) {
|
||||||
|
// launch the plugin process
|
||||||
|
client = plugin.NewClient(&plugin.ClientConfig{
|
||||||
|
HandshakeConfig: shared.HandshakeConfig,
|
||||||
|
Plugins: map[string]plugin.Plugin{
|
||||||
|
shared.Name: &shared.HashicorpPlugin{},
|
||||||
|
},
|
||||||
|
Cmd: exec.Command(path),
|
||||||
|
Logger: hclog.New(&hclog.LoggerOptions{
|
||||||
|
Name: shared.Name,
|
||||||
|
Output: os.Stdout,
|
||||||
|
Level: hclog.Info,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Connect via RPC
|
||||||
|
rpcClient, err := client.Client()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request the plugin
|
||||||
|
raw, err := rpcClient.Dispense(shared.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have a Function now! This feels like a normal interface
|
||||||
|
// implementation but is in fact over an RPC connection.
|
||||||
|
function := raw.(shared.FuncCaller)
|
||||||
|
return function, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Quit() {
|
||||||
|
client.Kill()
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
hclog "github.com/hashicorp/go-hclog"
|
||||||
|
"github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
|
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
// functionsMap stores plugin functions
|
||||||
|
type functionsMap map[string]reflect.Value
|
||||||
|
|
||||||
|
// functionPlugin implements the FuncCaller interface
|
||||||
|
type functionPlugin struct {
|
||||||
|
logger hclog.Logger
|
||||||
|
functions functionsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *functionPlugin) GetNames() ([]string, error) {
|
||||||
|
var names []string
|
||||||
|
for name := range p.functions {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *functionPlugin) Call(funcName string, args ...interface{}) (interface{}, error) {
|
||||||
|
p.logger.Info("call function", "funcName", funcName, "args", args)
|
||||||
|
|
||||||
|
fn, ok := p.functions[funcName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("function %s not found", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginShared.CallFunc(fn, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var functions = make(functionsMap)
|
||||||
|
|
||||||
|
func Register(funcName string, fn interface{}) {
|
||||||
|
if _, ok := functions[funcName]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
functions[funcName] = reflect.ValueOf(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Serve() {
|
||||||
|
funcPlugin := &functionPlugin{
|
||||||
|
logger: hclog.New(&hclog.LoggerOptions{
|
||||||
|
Name: pluginShared.Name,
|
||||||
|
Output: os.Stdout,
|
||||||
|
Level: hclog.Info,
|
||||||
|
}),
|
||||||
|
functions: functions,
|
||||||
|
}
|
||||||
|
var pluginMap = map[string]plugin.Plugin{
|
||||||
|
pluginShared.Name: &pluginShared.HashicorpPlugin{Impl: funcPlugin},
|
||||||
|
}
|
||||||
|
plugin.Serve(&plugin.ServeConfig{
|
||||||
|
HandshakeConfig: pluginShared.HandshakeConfig,
|
||||||
|
Plugins: pluginMap,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import "github.com/hashicorp/go-plugin"
|
||||||
|
|
||||||
|
const Name = "debugtalk"
|
||||||
|
|
||||||
|
// handshakeConfigs are used to just do a basic handshake between
|
||||||
|
// a plugin and host. If the handshake fails, a user friendly error is shown.
|
||||||
|
// This prevents users from executing bad plugins or executing a plugin
|
||||||
|
// directory. It is a UX feature, not a security feature.
|
||||||
|
var HandshakeConfig = plugin.HandshakeConfig{
|
||||||
|
ProtocolVersion: 1,
|
||||||
|
MagicCookieKey: "HttpRunnerPlus",
|
||||||
|
MagicCookieValue: Name,
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package plugin
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
@@ -83,23 +83,21 @@ func (s *functionRPCServer) Call(args interface{}, resp *interface{}) error {
|
|||||||
var err error
|
var err error
|
||||||
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
*resp, err = s.Impl.Call(f.Name, f.Args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).
|
log.Error().Err(err).Interface("args", args).Msg("function execution failed")
|
||||||
Interface("args", args).
|
|
||||||
Msg("function execution failed")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashicorpPlugin implements hashicorp's plugin.Plugin.
|
// HashicorpPlugin implements hashicorp's plugin.Plugin.
|
||||||
type hashicorpPlugin struct {
|
type HashicorpPlugin struct {
|
||||||
Impl FuncCaller
|
Impl FuncCaller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *hashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
func (p *HashicorpPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
|
||||||
return &functionRPCServer{Impl: p.Impl}, nil
|
return &functionRPCServer{Impl: p.Impl}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
func (HashicorpPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
|
||||||
return &functionRPC{client: c}, nil
|
return &functionRPC{client: c}, nil
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CallFunc calls function with arguments
|
||||||
|
// it is used when calling go plugin or builtin functions
|
||||||
|
func CallFunc(fn reflect.Value, args ...interface{}) (interface{}, error) {
|
||||||
|
fnArgsNum := fn.Type().NumIn()
|
||||||
|
if fnArgsNum > 0 && fn.Type().In(fnArgsNum-1).Kind() == reflect.Slice {
|
||||||
|
// last argument is slice, do not check arguments number
|
||||||
|
// e.g. ...interface{}
|
||||||
|
// e.g. a, b string, c ...interface{}
|
||||||
|
} else if fnArgsNum != len(args) {
|
||||||
|
// function arguments not match
|
||||||
|
return nil, fmt.Errorf("function arguments number not match, expect %d, got %d", fnArgsNum, len(args))
|
||||||
|
}
|
||||||
|
// arguments do not have slice, and arguments number matched
|
||||||
|
|
||||||
|
argumentsValue := make([]reflect.Value, len(args))
|
||||||
|
for index, argument := range args {
|
||||||
|
if argument == nil {
|
||||||
|
argumentsValue[index] = reflect.Zero(fn.Type().In(index))
|
||||||
|
} else {
|
||||||
|
argumentsValue[index] = reflect.ValueOf(args[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultValues := fn.Call(argumentsValue)
|
||||||
|
if resultValues == nil {
|
||||||
|
// no returns
|
||||||
|
return nil, nil
|
||||||
|
} else if len(resultValues) == 2 {
|
||||||
|
// return two arguments: interface{}, error
|
||||||
|
if resultValues[1].Interface() != nil {
|
||||||
|
return resultValues[0].Interface(), resultValues[1].Interface().(error)
|
||||||
|
} else {
|
||||||
|
return resultValues[0].Interface(), nil
|
||||||
|
}
|
||||||
|
} else if len(resultValues) == 1 {
|
||||||
|
// return one arguments: interface{}
|
||||||
|
return resultValues[0].Interface(), nil
|
||||||
|
} else {
|
||||||
|
// return more than 2 arguments, unexpected
|
||||||
|
err := fmt.Errorf("function should return at most 2 arguments")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user