diff --git a/client_test.go b/client_test.go index 74b74d12..5dc4fad6 100644 --- a/client_test.go +++ b/client_test.go @@ -8,7 +8,6 @@ import ( "net" "os" "path/filepath" - "regexp" "strings" "testing" "time" @@ -781,9 +780,14 @@ func TestClient_ping(t *testing.T) { } } -func TestClient_Logger(t *testing.T) { - buffer := bytes.NewBuffer([]byte{}) - stderr := io.MultiWriter(os.Stderr, buffer) +func TestClient_logger(t *testing.T) { + t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") }) + t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") }) +} + +func testClient_logger(t *testing.T, proto string) { + var buffer bytes.Buffer + stderr := io.MultiWriter(os.Stderr, &buffer) // Custom hclog.Logger clientLogger := hclog.New(&hclog.LoggerOptions{ Name: "test-logger", @@ -791,12 +795,13 @@ func TestClient_Logger(t *testing.T) { Output: stderr, }) - process := helperProcess("test-interface-logger") + process := helperProcess("test-interface-logger-" + proto) c := NewClient(&ClientConfig{ - Cmd: process, - HandshakeConfig: testHandshake, - Plugins: testPluginMap, - Logger: clientLogger, + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + Logger: clientLogger, + AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC}, }) defer c.Kill() @@ -817,22 +822,32 @@ func TestClient_Logger(t *testing.T) { t.Fatalf("bad: %#v", raw) } - // Discard everything else, and capture the - // output we care about - buffer.Reset() - impl.PrintKV("foo", "bar") - line, err := buffer.ReadString('\n') - if err != nil { - t.Fatal(err) - } - re, err := regexp.Compile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}-\d{4} \[[A-Z ]+\].*foo=bar`) - if err != nil { - t.Fatal(err) + { + // Discard everything else, and capture the output we care about + buffer.Reset() + impl.PrintKV("foo", "bar") + time.Sleep(100 * time.Millisecond) + line, err := buffer.ReadString('\n') + if err != nil { + t.Fatal(err) + } + if !strings.Contains(line, "foo=bar") { + t.Fatalf("bad: %q", line) + } } - re.MatchString(line) - matched := re.MatchString(line) - if !matched { - t.Fatalf("incorrect log output from plugin on PrintKV; got: %s", line) + + { + // Try an integer type + buffer.Reset() + impl.PrintKV("foo", 12) + time.Sleep(100 * time.Millisecond) + line, err := buffer.ReadString('\n') + if err != nil { + t.Fatal(err) + } + if !strings.Contains(line, "foo=12") { + t.Fatalf("bad: %q", line) + } } // Kill it diff --git a/log_entry.go b/log_entry.go index 76d5b5e9..2996c14c 100644 --- a/log_entry.go +++ b/log_entry.go @@ -2,8 +2,6 @@ package plugin import ( "encoding/json" - "fmt" - "strconv" "time" ) @@ -17,53 +15,8 @@ type logEntry struct { // logEntryKV is a key value pair within the Output payload type logEntryKV struct { - Key string `json:"key"` - Value string `json:"value"` -} - -// parseKVPairs transforms string inputs into []*logEntryKV -func parseKVPairs(kvs ...interface{}) ([]*logEntryKV, error) { - var result []*logEntryKV - if len(kvs)%2 != 0 { - return nil, fmt.Errorf("kv slice needs to be even number, got %d", len(kvs)) - } - for i := 0; i < len(kvs); i = i + 2 { - var val string - - switch st := kvs[i+1].(type) { - case string: - val = st - case int: - val = strconv.FormatInt(int64(st), 10) - case int64: - val = strconv.FormatInt(int64(st), 10) - case int32: - val = strconv.FormatInt(int64(st), 10) - case int16: - val = strconv.FormatInt(int64(st), 10) - case int8: - val = strconv.FormatInt(int64(st), 10) - case uint: - val = strconv.FormatUint(uint64(st), 10) - case uint64: - val = strconv.FormatUint(uint64(st), 10) - case uint32: - val = strconv.FormatUint(uint64(st), 10) - case uint16: - val = strconv.FormatUint(uint64(st), 10) - case uint8: - val = strconv.FormatUint(uint64(st), 10) - default: - val = fmt.Sprintf("%v", st) - } - - result = append(result, &logEntryKV{ - Key: kvs[i].(string), - Value: val, - }) - } - - return result, nil + Key string `json:"key"` + Value interface{} `json:"value"` } // flattenKVPairs is used to flatten KVPair slice into []interface{} @@ -109,16 +62,12 @@ func parseJSON(input string) (*logEntry, error) { } // Parse dynamic KV args from the hclog payload. - kvs := []interface{}{} for k, v := range raw { - kvs = append(kvs, k) - kvs = append(kvs, v.(string)) - } - pairs, err := parseKVPairs(kvs...) - if err != nil { - return nil, err + entry.KVPairs = append(entry.KVPairs, &logEntryKV{ + Key: k, + Value: v, + }) } - entry.KVPairs = pairs return entry, nil } diff --git a/plugin_test.go b/plugin_test.go index 0049cc55..5c16a428 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -31,7 +31,7 @@ var testHandshake = HandshakeConfig{ // testInterface is the test interface we use for plugins. type testInterface interface { Double(int) int - PrintKV(string, string) + PrintKV(string, interface{}) } // testInterfacePlugin is the implementation of Plugin to create @@ -41,7 +41,7 @@ type testInterfacePlugin struct { } func (p *testInterfacePlugin) Server(b *MuxBroker) (interface{}, error) { - return &testInterfaceServer{Impl: p.Impl}, nil + return &testInterfaceServer{Impl: p.impl()}, nil } func (p *testInterfacePlugin) Client(b *MuxBroker, c *rpc.Client) (interface{}, error) { @@ -49,7 +49,7 @@ func (p *testInterfacePlugin) Client(b *MuxBroker, c *rpc.Client) (interface{}, } func (p *testInterfacePlugin) GRPCServer(s *grpc.Server) error { - grpctest.RegisterTestServer(s, &testGRPCServer{Impl: new(testInterfaceImpl)}) + grpctest.RegisterTestServer(s, &testGRPCServer{Impl: p.impl()}) return nil } @@ -57,6 +57,20 @@ func (p *testInterfacePlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error return &testGRPCClient{Client: grpctest.NewTestClient(c)}, nil } +func (p *testInterfacePlugin) impl() testInterface { + if p.Impl != nil { + return p.Impl + } + + return &testInterfaceImpl{ + logger: hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }), + } +} + // testInterfaceImpl implements testInterface concretely type testInterfaceImpl struct { logger hclog.Logger @@ -64,7 +78,7 @@ type testInterfaceImpl struct { func (i *testInterfaceImpl) Double(v int) int { return v * 2 } -func (i *testInterfaceImpl) PrintKV(key, value string) { +func (i *testInterfaceImpl) PrintKV(key string, value interface{}) { i.logger.Info("PrintKV called", key, value) } @@ -83,8 +97,8 @@ func (impl *testInterfaceClient) Double(v int) int { return resp } -func (impl *testInterfaceClient) PrintKV(key, value string) { - err := impl.Client.Call("Plugin.PrintKV", map[string]string{ +func (impl *testInterfaceClient) PrintKV(key string, value interface{}) { + err := impl.Client.Call("Plugin.PrintKV", map[string]interface{}{ "key": key, "value": value, }, &struct{}{}) @@ -104,11 +118,8 @@ func (s *testInterfaceServer) Double(arg int, resp *int) error { return nil } -func (s *testInterfaceServer) PrintKV(args map[string]string, _ *struct{}) error { - // if s.Impl == nil { - // log.Println("s.Impl is nil") - // } - s.Impl.PrintKV(args["key"], args["value"]) +func (s *testInterfaceServer) PrintKV(args map[string]interface{}, _ *struct{}) error { + s.Impl.PrintKV(args["key"].(string), args["value"]) return nil } @@ -133,7 +144,19 @@ func (s *testGRPCServer) Double( func (s *testGRPCServer) PrintKV( ctx context.Context, req *grpctest.PrintKVRequest) (*grpctest.PrintKVResponse, error) { - s.Impl.PrintKV(req.Key, req.Value) + var v interface{} + switch rv := req.Value.(type) { + case *grpctest.PrintKVRequest_ValueString: + v = rv.ValueString + + case *grpctest.PrintKVRequest_ValueInt: + v = rv.ValueInt + + default: + panic(fmt.Sprintf("unknown value: %#v", req.Value)) + } + + s.Impl.PrintKV(req.Key, v) return &grpctest.PrintKVResponse{}, nil } @@ -154,11 +177,24 @@ func (c *testGRPCClient) Double(v int) int { return int(resp.Output) } -func (c *testGRPCClient) PrintKV(key, value string) { - _, err := c.Client.PrintKV(context.Background(), &grpctest.PrintKVRequest{ - Key: key, - Value: value, - }) +func (c *testGRPCClient) PrintKV(key string, value interface{}) { + req := &grpctest.PrintKVRequest{Key: key} + switch v := value.(type) { + case string: + req.Value = &grpctest.PrintKVRequest_ValueString{ + ValueString: v, + } + + case int: + req.Value = &grpctest.PrintKVRequest_ValueInt{ + ValueInt: int32(v), + } + + default: + panic(fmt.Sprintf("unknown type: %T", value)) + } + + _, err := c.Client.PrintKV(context.Background(), req) if err != nil { panic(err) } @@ -303,10 +339,18 @@ func TestHelperProcess(*testing.T) { // Shouldn't reach here but make sure we exit anyways os.Exit(0) - case "test-interface-logger": + case "test-interface-logger-netrpc": + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface-logger-grpc": Serve(&ServeConfig{ HandshakeConfig: testHandshake, Plugins: testPluginMap, + GRPCServer: DefaultGRPCServer, }) // Shouldn't reach here but make sure we exit anyways os.Exit(0) diff --git a/test/grpc/gen.go b/test/grpc/gen.go new file mode 100644 index 00000000..c14618a7 --- /dev/null +++ b/test/grpc/gen.go @@ -0,0 +1,3 @@ +package grpctest + +//go:generate protoc -I ./ ./test.proto --go_out=plugins=grpc:. diff --git a/test/grpc/test.pb.go b/test/grpc/test.pb.go index 0a4aa1a8..2acfd06e 100644 --- a/test/grpc/test.pb.go +++ b/test/grpc/test.pb.go @@ -68,8 +68,11 @@ func (m *TestResponse) GetOutput() int32 { } type PrintKVRequest struct { - Key string `protobuf:"bytes,1,opt,name=Key" json:"Key,omitempty"` - Value string `protobuf:"bytes,2,opt,name=Value" json:"Value,omitempty"` + Key string `protobuf:"bytes,1,opt,name=Key" json:"Key,omitempty"` + // Types that are valid to be assigned to Value: + // *PrintKVRequest_ValueString + // *PrintKVRequest_ValueInt + Value isPrintKVRequest_Value `protobuf_oneof:"Value"` } func (m *PrintKVRequest) Reset() { *m = PrintKVRequest{} } @@ -77,6 +80,27 @@ func (m *PrintKVRequest) String() string { return proto.CompactTextSt func (*PrintKVRequest) ProtoMessage() {} func (*PrintKVRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +type isPrintKVRequest_Value interface { + isPrintKVRequest_Value() +} + +type PrintKVRequest_ValueString struct { + ValueString string `protobuf:"bytes,2,opt,name=ValueString,oneof"` +} +type PrintKVRequest_ValueInt struct { + ValueInt int32 `protobuf:"varint,3,opt,name=ValueInt,oneof"` +} + +func (*PrintKVRequest_ValueString) isPrintKVRequest_Value() {} +func (*PrintKVRequest_ValueInt) isPrintKVRequest_Value() {} + +func (m *PrintKVRequest) GetValue() isPrintKVRequest_Value { + if m != nil { + return m.Value + } + return nil +} + func (m *PrintKVRequest) GetKey() string { if m != nil { return m.Key @@ -84,13 +108,85 @@ func (m *PrintKVRequest) GetKey() string { return "" } -func (m *PrintKVRequest) GetValue() string { - if m != nil { - return m.Value +func (m *PrintKVRequest) GetValueString() string { + if x, ok := m.GetValue().(*PrintKVRequest_ValueString); ok { + return x.ValueString } return "" } +func (m *PrintKVRequest) GetValueInt() int32 { + if x, ok := m.GetValue().(*PrintKVRequest_ValueInt); ok { + return x.ValueInt + } + return 0 +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*PrintKVRequest) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _PrintKVRequest_OneofMarshaler, _PrintKVRequest_OneofUnmarshaler, _PrintKVRequest_OneofSizer, []interface{}{ + (*PrintKVRequest_ValueString)(nil), + (*PrintKVRequest_ValueInt)(nil), + } +} + +func _PrintKVRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*PrintKVRequest) + // Value + switch x := m.Value.(type) { + case *PrintKVRequest_ValueString: + b.EncodeVarint(2<<3 | proto.WireBytes) + b.EncodeStringBytes(x.ValueString) + case *PrintKVRequest_ValueInt: + b.EncodeVarint(3<<3 | proto.WireVarint) + b.EncodeVarint(uint64(x.ValueInt)) + case nil: + default: + return fmt.Errorf("PrintKVRequest.Value has unexpected type %T", x) + } + return nil +} + +func _PrintKVRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*PrintKVRequest) + switch tag { + case 2: // Value.ValueString + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Value = &PrintKVRequest_ValueString{x} + return true, err + case 3: // Value.ValueInt + if wire != proto.WireVarint { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeVarint() + m.Value = &PrintKVRequest_ValueInt{int32(x)} + return true, err + default: + return false, nil + } +} + +func _PrintKVRequest_OneofSizer(msg proto.Message) (n int) { + m := msg.(*PrintKVRequest) + // Value + switch x := m.Value.(type) { + case *PrintKVRequest_ValueString: + n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.ValueString))) + n += len(x.ValueString) + case *PrintKVRequest_ValueInt: + n += proto.SizeVarint(3<<3 | proto.WireVarint) + n += proto.SizeVarint(uint64(x.ValueInt)) + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + type PrintKVResponse struct { } @@ -214,18 +310,20 @@ var _Test_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("test.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 206 bytes of a gzipped FileDescriptorProto + // 240 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x48, 0x2f, 0x2a, 0x48, 0x06, 0xf1, 0x95, 0x94, 0xb9, 0xb8, 0x43, 0x52, 0x8b, 0x4b, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x44, 0xb8, 0x58, 0x3d, 0xf3, 0x0a, 0x4a, 0x4b, 0x24, 0x18, 0x15, 0x18, 0x35, 0x58, 0x83, 0x20, 0x1c, 0x25, 0x35, 0x2e, 0x1e, 0x88, 0xa2, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x21, 0x31, 0x2e, 0x36, 0xff, - 0xd2, 0x12, 0x90, 0x32, 0x26, 0xb0, 0x32, 0x28, 0x4f, 0xc9, 0x82, 0x8b, 0x2f, 0xa0, 0x28, 0x33, + 0xd2, 0x12, 0x90, 0x32, 0x26, 0xb0, 0x32, 0x28, 0x4f, 0x29, 0x97, 0x8b, 0x2f, 0xa0, 0x28, 0x33, 0xaf, 0xc4, 0x3b, 0x0c, 0x66, 0x9e, 0x00, 0x17, 0xb3, 0x77, 0x6a, 0x25, 0xd8, 0x34, 0xce, 0x20, - 0x10, 0x13, 0x64, 0x43, 0x58, 0x62, 0x4e, 0x69, 0x2a, 0x58, 0x2b, 0x67, 0x10, 0x84, 0xa3, 0x24, - 0xc8, 0xc5, 0x0f, 0xd7, 0x09, 0xb1, 0xc4, 0xa8, 0x99, 0x91, 0x8b, 0x05, 0x64, 0xab, 0x90, 0x25, - 0x17, 0x9b, 0x4b, 0x7e, 0x69, 0x52, 0x4e, 0xaa, 0x90, 0xa8, 0x1e, 0xcc, 0xdd, 0x7a, 0x48, 0x8e, - 0x96, 0x12, 0x43, 0x17, 0x86, 0x98, 0xa0, 0xc4, 0x20, 0xe4, 0xc0, 0xc5, 0x0e, 0x35, 0x56, 0x48, - 0x02, 0xa1, 0x08, 0xd5, 0x8d, 0x52, 0x92, 0x58, 0x64, 0x60, 0x26, 0x24, 0xb1, 0x81, 0x03, 0xcc, - 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x88, 0xcf, 0xec, 0x95, 0x3e, 0x01, 0x00, 0x00, + 0x10, 0x53, 0x48, 0x89, 0x8b, 0x3b, 0x2c, 0x31, 0xa7, 0x34, 0x35, 0xb8, 0xa4, 0x28, 0x33, 0x2f, + 0x1d, 0x6c, 0x00, 0xa7, 0x07, 0x43, 0x10, 0xb2, 0xa0, 0x90, 0x0c, 0x17, 0x07, 0x98, 0xeb, 0x99, + 0x57, 0x22, 0xc1, 0x0c, 0xb2, 0xc1, 0x83, 0x21, 0x08, 0x2e, 0xe2, 0xc4, 0xce, 0xc5, 0x0a, 0x66, + 0x2b, 0x09, 0x72, 0xf1, 0xc3, 0xad, 0x83, 0xb8, 0xcc, 0xa8, 0x99, 0x91, 0x8b, 0x05, 0xe4, 0x54, + 0x21, 0x4b, 0x2e, 0x36, 0x97, 0xfc, 0xd2, 0xa4, 0x9c, 0x54, 0x21, 0x51, 0x3d, 0x98, 0x67, 0xf5, + 0x90, 0x7c, 0x2a, 0x25, 0x86, 0x2e, 0x0c, 0x31, 0x41, 0x89, 0x41, 0xc8, 0x81, 0x8b, 0x1d, 0x6a, + 0xac, 0x90, 0x04, 0x42, 0x11, 0xaa, 0xc7, 0xa4, 0x24, 0xb1, 0xc8, 0xc0, 0x4c, 0x48, 0x62, 0x03, + 0x87, 0xb2, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x34, 0x25, 0xf9, 0xb5, 0x73, 0x01, 0x00, 0x00, } diff --git a/test/grpc/test.proto b/test/grpc/test.proto index 695e4395..06b9b64c 100644 --- a/test/grpc/test.proto +++ b/test/grpc/test.proto @@ -11,11 +11,14 @@ message TestResponse { message PrintKVRequest { string Key = 1; - string Value = 2; + oneof Value { + string ValueString = 2; + int32 ValueInt = 3; + } } message PrintKVResponse { - + } service Test {