feat: support switch hashicorp plugin with environment HRP_PLUGIN_TYPE

This commit is contained in:
debugtalk
2022-03-04 20:34:24 +08:00
parent c27224a452
commit ea89a0b6a9
8 changed files with 73 additions and 21 deletions

View File

@@ -2,7 +2,7 @@
## 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
## v0.6.3 (2022-03-04)

View File

@@ -96,7 +96,7 @@ func Dump2JSON(data interface{}, path string) error {
return err
}
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)
if err != nil {
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{}) {
// convert value to json
if key == "body" {
b, _ := json.MarshalIndent(&value, "", "\t")
b, _ := json.MarshalIndent(&value, "", " ")
value = string(b)
}
formattedResponse[key] = value

View File

@@ -4,6 +4,7 @@ import (
jsoniter "github.com/json-iterator/go"
)
// replace with third-party json library to improve performance
var json = jsoniter.ConfigCompatibleWithStandardLibrary
var (

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
The golang official plugin is only supported on Linux, FreeBSD, and macOS. And this solution also has many drawbacks.

View File

@@ -1,6 +1,11 @@
package pluginInternal
import "github.com/hashicorp/go-plugin"
import (
"os"
"strings"
"github.com/hashicorp/go-plugin"
)
const PluginName = "debugtalk"
const RPCPluginName = PluginName + "_rpc"
@@ -15,3 +20,18 @@ var HandshakeConfig = plugin.HandshakeConfig{
MagicCookieKey: "HttpRunnerPlus",
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"
}

View File

@@ -30,14 +30,14 @@ func (m *functionGRPCClient) GetNames() ([]string, error) {
func (m *functionGRPCClient) Call(funcName string, funcArgs ...interface{}) (interface{}, error) {
log.Info().Str("funcName", funcName).Interface("funcArgs", funcArgs).Msg("call function via gRPC")
req := &proto.CallRequest{
Name: funcName,
}
funcArgBytes, err := json.Marshal(funcArgs)
if err != nil {
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)
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")
v, err := m.Impl.GetNames()
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 &proto.GetNamesResponse{Names: v}, err

View File

@@ -1,11 +1,13 @@
package pluginInternal
import (
"fmt"
"os"
"os/exec"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
@@ -19,7 +21,14 @@ type HashicorpPlugin struct {
}
func (p *HashicorpPlugin) Init(path string) error {
pluginName := GRPCPluginName
var pluginName string
if IsRPCPluginType() {
pluginName = RPCPluginName
} else {
pluginName = GRPCPluginName
}
// logger
loggerOptions := &hclog.LoggerOptions{
Name: pluginName,
Output: os.Stdout,
@@ -29,6 +38,11 @@ func (p *HashicorpPlugin) Init(path string) error {
} else {
loggerOptions.Level = hclog.Info
}
// cmd
cmd := exec.Command(path)
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=%s", hrpPluginTypeEnvName, hrpPluginType))
// launch the plugin process
client = plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
@@ -36,7 +50,7 @@ func (p *HashicorpPlugin) Init(path string) error {
RPCPluginName: &RPCPlugin{},
GRPCPluginName: &GRPCPlugin{},
},
Cmd: exec.Command(path),
Cmd: cmd,
Logger: hclog.New(loggerOptions),
AllowedProtocols: []plugin.Protocol{
plugin.ProtocolNetRPC,
@@ -44,18 +58,16 @@ func (p *HashicorpPlugin) Init(path string) error {
},
})
// Connect via RPC
// Connect via RPC/gRPC
rpcClient, err := client.Client()
if err != nil {
log.Error().Err(err).Msg("connect plugin via RPC failed")
return err
return errors.Wrap(err, fmt.Sprintf("connect %s plugin failed", hrpPluginType))
}
// Request the plugin
raw, err := rpcClient.Dispense(pluginName)
if err != nil {
log.Error().Err(err).Msg("request plugin failed")
return err
return errors.Wrap(err, fmt.Sprintf("request %s plugin failed", hrpPluginType))
}
// We should have a Function now! This feels like a normal interface

View File

@@ -51,8 +51,8 @@ func Register(funcName string, fn interface{}) {
functions[funcName] = reflect.ValueOf(fn)
}
// Serve starts a plugin server process in RPC mode.
func ServeRPC() {
// serveRPC starts a plugin server process in RPC mode.
func serveRPC() {
log.Info().Msg("start plugin server in RPC mode")
funcPlugin := &functionPlugin{
logger: hclog.New(&hclog.LoggerOptions{
@@ -72,8 +72,8 @@ func ServeRPC() {
})
}
// Serve starts a plugin server process in gRPC mode.
func ServeGRPC() {
// serveGRPC starts a plugin server process in gRPC mode.
func serveGRPC() {
log.Info().Msg("start plugin server in gRPC mode")
funcPlugin := &functionPlugin{
logger: hclog.New(&hclog.LoggerOptions{
@@ -95,4 +95,11 @@ func ServeGRPC() {
}
// default to run plugin in gRPC mode
var Serve = ServeGRPC
func Serve() {
if pluginInternal.IsRPCPluginType() {
serveRPC()
} else {
// default
serveGRPC()
}
}