feat: support switch hashicorp plugin with environment HRP_PLUGIN_TYPE

This commit is contained in:
debugtalk
2022-03-04 20:50:37 +08:00
parent c27224a452
commit ea89a0b6a9
8 changed files with 73 additions and 21 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
## v0.7.0 (2022-03-04) ## v0.7.0 (2022-03-04)
- refactor: replace hashicorp plugin from net/rpc to gPRC mode - feat: both support gPRC(default) and net/rpc mode in hashicorp plugin, switch with environment `HRP_PLUGIN_TYPE`
- refactor: replace builtin json library with json-iterator/go to improve performance - refactor: replace builtin json library with json-iterator/go to improve performance
## v0.6.3 (2022-03-04) ## v0.6.3 (2022-03-04)
+2 -2
View File
@@ -96,7 +96,7 @@ func Dump2JSON(data interface{}, path string) error {
return err return err
} }
log.Info().Str("path", path).Msg("dump data to json") log.Info().Str("path", path).Msg("dump data to json")
file, _ := json.MarshalIndent(data, "", "\t") file, _ := json.MarshalIndent(data, "", " ")
err = os.WriteFile(path, file, 0644) err = os.WriteFile(path, file, 0644)
if err != nil { if err != nil {
log.Error().Err(err).Msg("dump json path failed") log.Error().Err(err).Msg("dump json path failed")
@@ -137,7 +137,7 @@ func FormatResponse(raw interface{}) interface{} {
for key, value := range raw.(map[string]interface{}) { for key, value := range raw.(map[string]interface{}) {
// convert value to json // convert value to json
if key == "body" { if key == "body" {
b, _ := json.MarshalIndent(&value, "", "\t") b, _ := json.MarshalIndent(&value, "", " ")
value = string(b) value = string(b)
} }
formattedResponse[key] = value formattedResponse[key] = value
+1
View File
@@ -4,6 +4,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
) )
// replace with third-party json library to improve performance
var json = jsoniter.ConfigCompatibleWithStandardLibrary var json = jsoniter.ConfigCompatibleWithStandardLibrary
var ( var (
+12
View File
@@ -99,6 +99,18 @@ Then, you can call your defined plugin function in your `YAML/JSON` testcase at
} }
``` ```
### rpc vs. gRPC
HttpRunner+ has both supported `net/rpc` and `gRPC` in [hashicorp/plugin]. It is recommended to use `gRPC` and this is the default choice.
If you want to run plugin in `net/rpc` mode, you can set an environment variable `HRP_PLUGIN_TYPE=rpc`.
```bash
$ export HRP_PLUGIN_TYPE=rpc
$ hrp run examples/demo.json
$ hrp boom examples/demo.json
```
## go plugin ## go plugin
The golang official plugin is only supported on Linux, FreeBSD, and macOS. And this solution also has many drawbacks. The golang official plugin is only supported on Linux, FreeBSD, and macOS. And this solution also has many drawbacks.
+21 -1
View File
@@ -1,6 +1,11 @@
package pluginInternal package pluginInternal
import "github.com/hashicorp/go-plugin" import (
"os"
"strings"
"github.com/hashicorp/go-plugin"
)
const PluginName = "debugtalk" const PluginName = "debugtalk"
const RPCPluginName = PluginName + "_rpc" const RPCPluginName = PluginName + "_rpc"
@@ -15,3 +20,18 @@ var HandshakeConfig = plugin.HandshakeConfig{
MagicCookieKey: "HttpRunnerPlus", MagicCookieKey: "HttpRunnerPlus",
MagicCookieValue: PluginName, MagicCookieValue: PluginName,
} }
const hrpPluginTypeEnvName = "HRP_PLUGIN_TYPE"
var hrpPluginType string
func init() {
hrpPluginType = strings.ToLower(os.Getenv(hrpPluginTypeEnvName))
if hrpPluginType == "" {
hrpPluginType = "grpc" // default
}
}
func IsRPCPluginType() bool {
return hrpPluginType == "rpc"
}
+5 -5
View File
@@ -30,14 +30,14 @@ func (m *functionGRPCClient) GetNames() ([]string, error) {
func (m *functionGRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) { func (m *functionGRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via gRPC") log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via gRPC")
req := &proto.CallRequest{
Name: funcName,
}
funcArgBytes, err := json.Marshal(funcArgs) funcArgBytes, err := json.Marshal(funcArgs)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal Call() funcArgs") return nil, errors.Wrap(err, "failed to marshal Call() funcArgs")
} }
req.Args = funcArgBytes req := &proto.CallRequest{
Name: funcName,
Args: funcArgBytes,
}
response, err := m.client.Call(context.Background(), req) response, err := m.client.Call(context.Background(), req)
if err != nil { if err != nil {
@@ -65,7 +65,7 @@ func (m *functionGRPCServer) GetNames(ctx context.Context, req *proto.Empty) (*p
log.Info().Interface("req", req).Msg("gRPC GetNames() called on plugin side") log.Info().Interface("req", req).Msg("gRPC GetNames() called on plugin side")
v, err := m.Impl.GetNames() v, err := m.Impl.GetNames()
if err != nil { if err != nil {
log.Error().Err(err).Msg("gRPC GetNames execution failed") log.Error().Err(err).Msg("gRPC GetNames() execution failed")
return nil, err return nil, err
} }
return &proto.GetNamesResponse{Names: v}, err return &proto.GetNamesResponse{Names: v}, err
+19 -7
View File
@@ -1,11 +1,13 @@
package pluginInternal package pluginInternal
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
"github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -19,7 +21,14 @@ type HashicorpPlugin struct {
} }
func (p *HashicorpPlugin) Init(path string) error { func (p *HashicorpPlugin) Init(path string) error {
pluginName := GRPCPluginName var pluginName string
if IsRPCPluginType() {
pluginName = RPCPluginName
} else {
pluginName = GRPCPluginName
}
// logger
loggerOptions := &hclog.LoggerOptions{ loggerOptions := &hclog.LoggerOptions{
Name: pluginName, Name: pluginName,
Output: os.Stdout, Output: os.Stdout,
@@ -29,6 +38,11 @@ func (p *HashicorpPlugin) Init(path string) error {
} else { } else {
loggerOptions.Level = hclog.Info loggerOptions.Level = hclog.Info
} }
// cmd
cmd := exec.Command(path)
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", hrpPluginTypeEnvName, hrpPluginType))
// launch the plugin process // launch the plugin process
client = plugin.NewClient(&plugin.ClientConfig{ client = plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig, HandshakeConfig: HandshakeConfig,
@@ -36,7 +50,7 @@ func (p *HashicorpPlugin) Init(path string) error {
RPCPluginName: &RPCPlugin{}, RPCPluginName: &RPCPlugin{},
GRPCPluginName: &GRPCPlugin{}, GRPCPluginName: &GRPCPlugin{},
}, },
Cmd: exec.Command(path), Cmd: cmd,
Logger: hclog.New(loggerOptions), Logger: hclog.New(loggerOptions),
AllowedProtocols: []plugin.Protocol{ AllowedProtocols: []plugin.Protocol{
plugin.ProtocolNetRPC, plugin.ProtocolNetRPC,
@@ -44,18 +58,16 @@ func (p *HashicorpPlugin) Init(path string) error {
}, },
}) })
// Connect via RPC // Connect via RPC/gRPC
rpcClient, err := client.Client() rpcClient, err := client.Client()
if err != nil { if err != nil {
log.Error().Err(err).Msg("connect plugin via RPC failed") return errors.Wrap(err, fmt.Sprintf("connect %s plugin failed", hrpPluginType))
return err
} }
// Request the plugin // Request the plugin
raw, err := rpcClient.Dispense(pluginName) raw, err := rpcClient.Dispense(pluginName)
if err != nil { if err != nil {
log.Error().Err(err).Msg("request plugin failed") return errors.Wrap(err, fmt.Sprintf("request %s plugin failed", hrpPluginType))
return err
} }
// We should have a Function now! This feels like a normal interface // We should have a Function now! This feels like a normal interface
+12 -5
View File
@@ -51,8 +51,8 @@ func Register(funcName string, fn interface{}) {
functions[funcName] = reflect.ValueOf(fn) functions[funcName] = reflect.ValueOf(fn)
} }
// Serve starts a plugin server process in RPC mode. // serveRPC starts a plugin server process in RPC mode.
func ServeRPC() { func serveRPC() {
log.Info().Msg("start plugin server in RPC mode") log.Info().Msg("start plugin server in RPC mode")
funcPlugin := &functionPlugin{ funcPlugin := &functionPlugin{
logger: hclog.New(&hclog.LoggerOptions{ logger: hclog.New(&hclog.LoggerOptions{
@@ -72,8 +72,8 @@ func ServeRPC() {
}) })
} }
// Serve starts a plugin server process in gRPC mode. // serveGRPC starts a plugin server process in gRPC mode.
func ServeGRPC() { func serveGRPC() {
log.Info().Msg("start plugin server in gRPC mode") log.Info().Msg("start plugin server in gRPC mode")
funcPlugin := &functionPlugin{ funcPlugin := &functionPlugin{
logger: hclog.New(&hclog.LoggerOptions{ logger: hclog.New(&hclog.LoggerOptions{
@@ -95,4 +95,11 @@ func ServeGRPC() {
} }
// default to run plugin in gRPC mode // default to run plugin in gRPC mode
var Serve = ServeGRPC func Serve() {
if pluginInternal.IsRPCPluginType() {
serveRPC()
} else {
// default
serveGRPC()
}
}