diff --git a/README.md b/README.md index 27ba023b8..947db4860 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,14 @@ gRPC API: * `/grpc.health.v1.Health/Check` health checking * `/grpc.EchoService/Echo` echos the received content * `/grpc.VersionService/Version` returns podinfo version and Git commit hash +* `/grpc.DelayService/Delay` returns a successful response after the given seconds in the body of gRPC request +* `/grpc.EnvService/Env` returns environment variables as a JSON array +* `/grpc.HeaderService/Header` returns the headers present in the gRPC request. Any custom header can also be given as a part of request and that can be returned using this API +* `/grpc.InfoService/Info` returns the runtime information +* `/grpc.PanicService/Panic` crashes the process with gRPC status code as '1 CANCELLED' +* `/grpc.StatusService/Status` returns the gRPC Status code given in the request body +* `/grpc.TokenService/TokenGenerate` issues a JWT token valid for one minute +* `/grpc.TokenService/TokenValidate` validates the JWT token Web UI: diff --git a/cmd/podinfo/main.go b/cmd/podinfo/main.go index f05a6de76..f8f804a3e 100644 --- a/cmd/podinfo/main.go +++ b/cmd/podinfo/main.go @@ -139,7 +139,7 @@ func main() { if grpcCfg.Port > 0 { grpcSrv, _ := grpc.NewServer(&grpcCfg, logger) //grpcinfoSrv, _ := grpc.NewInfoServer(&grpcCfg) - + grpcServer = grpcSrv.ListenAndServe() } diff --git a/pkg/api/grpc/delay.go b/pkg/api/grpc/delay.go new file mode 100644 index 000000000..64b4355fb --- /dev/null +++ b/pkg/api/grpc/delay.go @@ -0,0 +1,21 @@ +package grpc + +import ( + "context" + "time" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/delay" + "go.uber.org/zap" +) + +type DelayServer struct { + pb.UnimplementedDelayServiceServer + config *Config + logger *zap.Logger +} + +func (s *DelayServer) Delay(ctx context.Context, delayInput *pb.DelayRequest) (*pb.DelayResponse, error) { + + time.Sleep(time.Duration(delayInput.Seconds) * time.Second) + return &pb.DelayResponse{Message: delayInput.Seconds}, nil +} diff --git a/pkg/api/grpc/delay/delay.pb.go b/pkg/api/grpc/delay/delay.pb.go new file mode 100644 index 000000000..7d06cbb58 --- /dev/null +++ b/pkg/api/grpc/delay/delay.pb.go @@ -0,0 +1,211 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: delay/delay.proto + +package delay + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type DelayRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"` +} + +func (x *DelayRequest) Reset() { + *x = DelayRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_delay_delay_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DelayRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DelayRequest) ProtoMessage() {} + +func (x *DelayRequest) ProtoReflect() protoreflect.Message { + mi := &file_delay_delay_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DelayRequest.ProtoReflect.Descriptor instead. +func (*DelayRequest) Descriptor() ([]byte, []int) { + return file_delay_delay_proto_rawDescGZIP(), []int{0} +} + +func (x *DelayRequest) GetSeconds() int64 { + if x != nil { + return x.Seconds + } + return 0 +} + +type DelayResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message int64 `protobuf:"varint,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *DelayResponse) Reset() { + *x = DelayResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_delay_delay_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DelayResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DelayResponse) ProtoMessage() {} + +func (x *DelayResponse) ProtoReflect() protoreflect.Message { + mi := &file_delay_delay_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DelayResponse.ProtoReflect.Descriptor instead. +func (*DelayResponse) Descriptor() ([]byte, []int) { + return file_delay_delay_proto_rawDescGZIP(), []int{1} +} + +func (x *DelayResponse) GetMessage() int64 { + if x != nil { + return x.Message + } + return 0 +} + +var File_delay_delay_proto protoreflect.FileDescriptor + +var file_delay_delay_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x2f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x65, + 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, + 0x44, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x34, 0x0a, 0x05, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x13, 0x2e, 0x64, 0x65, 0x6c, 0x61, 0x79, + 0x2e, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, + 0x64, 0x65, 0x6c, 0x61, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x64, 0x65, 0x6c, 0x61, 0x79, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_delay_delay_proto_rawDescOnce sync.Once + file_delay_delay_proto_rawDescData = file_delay_delay_proto_rawDesc +) + +func file_delay_delay_proto_rawDescGZIP() []byte { + file_delay_delay_proto_rawDescOnce.Do(func() { + file_delay_delay_proto_rawDescData = protoimpl.X.CompressGZIP(file_delay_delay_proto_rawDescData) + }) + return file_delay_delay_proto_rawDescData +} + +var file_delay_delay_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_delay_delay_proto_goTypes = []interface{}{ + (*DelayRequest)(nil), // 0: delay.DelayRequest + (*DelayResponse)(nil), // 1: delay.DelayResponse +} +var file_delay_delay_proto_depIdxs = []int32{ + 0, // 0: delay.DelayService.Delay:input_type -> delay.DelayRequest + 1, // 1: delay.DelayService.Delay:output_type -> delay.DelayResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_delay_delay_proto_init() } +func file_delay_delay_proto_init() { + if File_delay_delay_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_delay_delay_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelayRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_delay_delay_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DelayResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_delay_delay_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_delay_delay_proto_goTypes, + DependencyIndexes: file_delay_delay_proto_depIdxs, + MessageInfos: file_delay_delay_proto_msgTypes, + }.Build() + File_delay_delay_proto = out.File + file_delay_delay_proto_rawDesc = nil + file_delay_delay_proto_goTypes = nil + file_delay_delay_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/delay/delay.proto b/pkg/api/grpc/delay/delay.proto new file mode 100644 index 000000000..b535a808b --- /dev/null +++ b/pkg/api/grpc/delay/delay.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option go_package = "./delay"; + +package delay; + +message DelayRequest { + int64 seconds = 1; +} + +message DelayResponse { + int64 message = 1; +} + +service DelayService { + rpc Delay (DelayRequest) returns (DelayResponse) {} +} \ No newline at end of file diff --git a/pkg/api/grpc/delay/delay_grpc.pb.go b/pkg/api/grpc/delay/delay_grpc.pb.go new file mode 100644 index 000000000..11c704507 --- /dev/null +++ b/pkg/api/grpc/delay/delay_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: delay/delay.proto + +package delay + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// DelayServiceClient is the client API for DelayService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DelayServiceClient interface { + Delay(ctx context.Context, in *DelayRequest, opts ...grpc.CallOption) (*DelayResponse, error) +} + +type delayServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDelayServiceClient(cc grpc.ClientConnInterface) DelayServiceClient { + return &delayServiceClient{cc} +} + +func (c *delayServiceClient) Delay(ctx context.Context, in *DelayRequest, opts ...grpc.CallOption) (*DelayResponse, error) { + out := new(DelayResponse) + err := c.cc.Invoke(ctx, "/delay.DelayService/Delay", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DelayServiceServer is the server API for DelayService service. +// All implementations must embed UnimplementedDelayServiceServer +// for forward compatibility +type DelayServiceServer interface { + Delay(context.Context, *DelayRequest) (*DelayResponse, error) + mustEmbedUnimplementedDelayServiceServer() +} + +// UnimplementedDelayServiceServer must be embedded to have forward compatible implementations. +type UnimplementedDelayServiceServer struct { +} + +func (UnimplementedDelayServiceServer) Delay(context.Context, *DelayRequest) (*DelayResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delay not implemented") +} +func (UnimplementedDelayServiceServer) mustEmbedUnimplementedDelayServiceServer() {} + +// UnsafeDelayServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DelayServiceServer will +// result in compilation errors. +type UnsafeDelayServiceServer interface { + mustEmbedUnimplementedDelayServiceServer() +} + +func RegisterDelayServiceServer(s grpc.ServiceRegistrar, srv DelayServiceServer) { + s.RegisterService(&DelayService_ServiceDesc, srv) +} + +func _DelayService_Delay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DelayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DelayServiceServer).Delay(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/delay.DelayService/Delay", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DelayServiceServer).Delay(ctx, req.(*DelayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DelayService_ServiceDesc is the grpc.ServiceDesc for DelayService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DelayService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "delay.DelayService", + HandlerType: (*DelayServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Delay", + Handler: _DelayService_Delay_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "delay/delay.proto", +} diff --git a/pkg/api/grpc/delay_test.go b/pkg/api/grpc/delay_test.go new file mode 100644 index 000000000..fff85fd22 --- /dev/null +++ b/pkg/api/grpc/delay_test.go @@ -0,0 +1,75 @@ +package grpc + +import ( + "context" + "fmt" + "log" + "net" + "regexp" + "strconv" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/delay" + "google.golang.org/grpc" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcDelay(t *testing.T) { + + // Server initialization + // bufconn => uses in-memory connection instead of system network I/O + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + delay.RegisterDelayServiceServer(srv, &DelayServer{}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + // - Test + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := delay.NewDelayServiceClient(conn) + res, err := client.Delay(context.Background(), &delay.DelayRequest{Seconds: 3}) + + // Check the status code is what we expect. + if _, ok := status.FromError(err); !ok { + t.Errorf("Delay returned type %T, want %T", err, status.Error) + } + + if res != nil { + fmt.Printf("res %v\n", res) + } + + // Check the response body is what we expect. Here we expect the response to be "3" as the delay is set to 3 seconds. + expected := "3" + r := regexp.MustCompile(expected) + if !r.MatchString(strconv.FormatInt(res.Message, 10)) { + t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", + res.Message, expected) + } +} diff --git a/pkg/api/grpc/echo.go b/pkg/api/grpc/echo.go index 2e850941f..6e32469df 100644 --- a/pkg/api/grpc/echo.go +++ b/pkg/api/grpc/echo.go @@ -13,8 +13,8 @@ type echoServer struct { logger *zap.Logger } -func (s *echoServer) Echo (ctx context.Context, message *echo.Message) (*echo.Message, error){ +func (s *echoServer) Echo(ctx context.Context, message *echo.Message) (*echo.Message, error) { s.logger.Info("Received message body from client:", zap.String("input body", message.Body)) - return &echo.Message {Body: message.Body}, nil + return &echo.Message{Body: message.Body}, nil } diff --git a/pkg/api/grpc/echo_test.go b/pkg/api/grpc/echo_test.go index 4f1812d85..ec21c7368 100644 --- a/pkg/api/grpc/echo_test.go +++ b/pkg/api/grpc/echo_test.go @@ -15,9 +15,7 @@ import ( func TestGrpcEcho(t *testing.T) { - // Server initialization - // bufconn => uses in-memory connection instead of system network I/O - lis := bufconn.Listen(1024*1024) + lis := bufconn.Listen(1024 * 1024) t.Cleanup(func() { lis.Close() }) @@ -30,19 +28,18 @@ func TestGrpcEcho(t *testing.T) { echo.RegisterEchoServiceServer(srv, &echoServer{config: s.config, logger: s.logger}) - go func(){ + go func() { if err := srv.Serve(lis); err != nil { log.Fatalf("srv.Serve %v", err) } }() - // - Test - dialer := func(context.Context, string) (net.Conn, error){ + dialer := func(context.Context, string) (net.Conn, error) { return lis.Dial() } ctx := context.Background() - + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) t.Cleanup(func() { conn.Close() @@ -53,18 +50,16 @@ func TestGrpcEcho(t *testing.T) { } client := echo.NewEchoServiceClient(conn) - res , err := client.Echo(context.Background(), &echo.Message{Body:"test123-test"}) + res, err := client.Echo(context.Background(), &echo.Message{Body: "test123-test"}) - // Check the status code is what we expect. if _, ok := status.FromError(err); !ok { t.Errorf("Echo returned type %T, want %T", err, status.Error) } - // Check the response body is what we expect. expected := ".*body.*test123-test.*" r := regexp.MustCompile(expected) if !r.MatchString(res.String()) { t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", res, expected) } -} \ No newline at end of file +} diff --git a/pkg/api/grpc/env.go b/pkg/api/grpc/env.go new file mode 100644 index 000000000..f870e76b1 --- /dev/null +++ b/pkg/api/grpc/env.go @@ -0,0 +1,19 @@ +package grpc + +import ( + "context" + "go.uber.org/zap" + "os" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/env" +) + +type EnvServer struct { + pb.UnimplementedEnvServiceServer + config *Config + logger *zap.Logger +} + +func (s *EnvServer) Env(ctx context.Context, envInput *pb.EnvRequest) (*pb.EnvResponse, error) { + return &pb.EnvResponse{EnvVars: os.Environ()}, nil +} diff --git a/pkg/api/grpc/env/env.pb.go b/pkg/api/grpc/env/env.pb.go new file mode 100644 index 000000000..59789b8e3 --- /dev/null +++ b/pkg/api/grpc/env/env.pb.go @@ -0,0 +1,199 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: env/env.proto + +package env + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type EnvRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *EnvRequest) Reset() { + *x = EnvRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_env_env_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EnvRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnvRequest) ProtoMessage() {} + +func (x *EnvRequest) ProtoReflect() protoreflect.Message { + mi := &file_env_env_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnvRequest.ProtoReflect.Descriptor instead. +func (*EnvRequest) Descriptor() ([]byte, []int) { + return file_env_env_proto_rawDescGZIP(), []int{0} +} + +type EnvResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvVars []string `protobuf:"bytes,1,rep,name=envVars,proto3" json:"envVars,omitempty"` +} + +func (x *EnvResponse) Reset() { + *x = EnvResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_env_env_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EnvResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnvResponse) ProtoMessage() {} + +func (x *EnvResponse) ProtoReflect() protoreflect.Message { + mi := &file_env_env_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnvResponse.ProtoReflect.Descriptor instead. +func (*EnvResponse) Descriptor() ([]byte, []int) { + return file_env_env_proto_rawDescGZIP(), []int{1} +} + +func (x *EnvResponse) GetEnvVars() []string { + if x != nil { + return x.EnvVars + } + return nil +} + +var File_env_env_proto protoreflect.FileDescriptor + +var file_env_env_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x65, 0x6e, 0x76, 0x2f, 0x65, 0x6e, 0x76, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x03, 0x65, 0x6e, 0x76, 0x22, 0x0c, 0x0a, 0x0a, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x27, 0x0a, 0x0b, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x73, 0x32, 0x38, 0x0a, 0x0a, 0x45, + 0x6e, 0x76, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x03, 0x45, 0x6e, 0x76, + 0x12, 0x0f, 0x2e, 0x65, 0x6e, 0x76, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x10, 0x2e, 0x65, 0x6e, 0x76, 0x2e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x07, 0x5a, 0x05, 0x2e, 0x2f, 0x65, 0x6e, 0x76, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_env_env_proto_rawDescOnce sync.Once + file_env_env_proto_rawDescData = file_env_env_proto_rawDesc +) + +func file_env_env_proto_rawDescGZIP() []byte { + file_env_env_proto_rawDescOnce.Do(func() { + file_env_env_proto_rawDescData = protoimpl.X.CompressGZIP(file_env_env_proto_rawDescData) + }) + return file_env_env_proto_rawDescData +} + +var file_env_env_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_env_env_proto_goTypes = []interface{}{ + (*EnvRequest)(nil), // 0: env.EnvRequest + (*EnvResponse)(nil), // 1: env.EnvResponse +} +var file_env_env_proto_depIdxs = []int32{ + 0, // 0: env.EnvService.Env:input_type -> env.EnvRequest + 1, // 1: env.EnvService.Env:output_type -> env.EnvResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_env_env_proto_init() } +func file_env_env_proto_init() { + if File_env_env_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_env_env_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EnvRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_env_env_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EnvResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_env_env_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_env_env_proto_goTypes, + DependencyIndexes: file_env_env_proto_depIdxs, + MessageInfos: file_env_env_proto_msgTypes, + }.Build() + File_env_env_proto = out.File + file_env_env_proto_rawDesc = nil + file_env_env_proto_goTypes = nil + file_env_env_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/env/env.proto b/pkg/api/grpc/env/env.proto new file mode 100644 index 000000000..6349230bc --- /dev/null +++ b/pkg/api/grpc/env/env.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option go_package = "./env"; + +package env; + +message EnvRequest {} + +message EnvResponse { + repeated string envVars = 1; +} + +service EnvService { + rpc Env (EnvRequest) returns (EnvResponse) {} +} \ No newline at end of file diff --git a/pkg/api/grpc/env/env_grpc.pb.go b/pkg/api/grpc/env/env_grpc.pb.go new file mode 100644 index 000000000..5ebf80d23 --- /dev/null +++ b/pkg/api/grpc/env/env_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: env/env.proto + +package env + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// EnvServiceClient is the client API for EnvService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type EnvServiceClient interface { + Env(ctx context.Context, in *EnvRequest, opts ...grpc.CallOption) (*EnvResponse, error) +} + +type envServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewEnvServiceClient(cc grpc.ClientConnInterface) EnvServiceClient { + return &envServiceClient{cc} +} + +func (c *envServiceClient) Env(ctx context.Context, in *EnvRequest, opts ...grpc.CallOption) (*EnvResponse, error) { + out := new(EnvResponse) + err := c.cc.Invoke(ctx, "/env.EnvService/Env", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EnvServiceServer is the server API for EnvService service. +// All implementations must embed UnimplementedEnvServiceServer +// for forward compatibility +type EnvServiceServer interface { + Env(context.Context, *EnvRequest) (*EnvResponse, error) + mustEmbedUnimplementedEnvServiceServer() +} + +// UnimplementedEnvServiceServer must be embedded to have forward compatible implementations. +type UnimplementedEnvServiceServer struct { +} + +func (UnimplementedEnvServiceServer) Env(context.Context, *EnvRequest) (*EnvResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Env not implemented") +} +func (UnimplementedEnvServiceServer) mustEmbedUnimplementedEnvServiceServer() {} + +// UnsafeEnvServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to EnvServiceServer will +// result in compilation errors. +type UnsafeEnvServiceServer interface { + mustEmbedUnimplementedEnvServiceServer() +} + +func RegisterEnvServiceServer(s grpc.ServiceRegistrar, srv EnvServiceServer) { + s.RegisterService(&EnvService_ServiceDesc, srv) +} + +func _EnvService_Env_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EnvRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EnvServiceServer).Env(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/env.EnvService/Env", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EnvServiceServer).Env(ctx, req.(*EnvRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// EnvService_ServiceDesc is the grpc.ServiceDesc for EnvService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var EnvService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "env.EnvService", + HandlerType: (*EnvServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Env", + Handler: _EnvService_Env_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "env/env.proto", +} diff --git a/pkg/api/grpc/env_test.go b/pkg/api/grpc/env_test.go new file mode 100644 index 000000000..ed9e7c30a --- /dev/null +++ b/pkg/api/grpc/env_test.go @@ -0,0 +1,64 @@ +package grpc + +import ( + "context" + "log" + "net" + "regexp" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/env" + "google.golang.org/grpc" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcEnv(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + env.RegisterEnvServiceServer(srv, &EnvServer{}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := env.NewEnvServiceClient(conn) + res, err := client.Env(context.Background(), &env.EnvRequest{}) + + if _, ok := status.FromError(err); !ok { + t.Errorf("Env returned type %T, want %T", err, status.Error) + } + + expected := ".*PATH.*" + r := regexp.MustCompile(expected) + if !r.MatchString(res.String()) { + t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", + res, expected) + } +} diff --git a/pkg/api/grpc/headers.go b/pkg/api/grpc/headers.go new file mode 100644 index 000000000..a2f494418 --- /dev/null +++ b/pkg/api/grpc/headers.go @@ -0,0 +1,34 @@ +package grpc + +import ( + "context" + "strings" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/headers" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type HeaderServer struct { + pb.UnimplementedHeaderServiceServer + config *Config + logger *zap.Logger +} + +func (s *HeaderServer) Header(ctx context.Context, in *pb.HeaderRequest) (*pb.HeaderResponse, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata") + } + + // Creating slices beacause echoing the header metadata can't be predetermined by the proto contract + res := []string{} + for i, e := range md { + res = append(res, i+"="+strings.Join(e, ",")) + } + + return &pb.HeaderResponse{Headers: res}, nil + +} diff --git a/pkg/api/grpc/headers/headers.pb.go b/pkg/api/grpc/headers/headers.pb.go new file mode 100644 index 000000000..573212b67 --- /dev/null +++ b/pkg/api/grpc/headers/headers.pb.go @@ -0,0 +1,201 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: headers/headers.proto + +package header + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HeaderRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *HeaderRequest) Reset() { + *x = HeaderRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_headers_headers_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeaderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeaderRequest) ProtoMessage() {} + +func (x *HeaderRequest) ProtoReflect() protoreflect.Message { + mi := &file_headers_headers_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeaderRequest.ProtoReflect.Descriptor instead. +func (*HeaderRequest) Descriptor() ([]byte, []int) { + return file_headers_headers_proto_rawDescGZIP(), []int{0} +} + +type HeaderResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Headers []string `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` +} + +func (x *HeaderResponse) Reset() { + *x = HeaderResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_headers_headers_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeaderResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeaderResponse) ProtoMessage() {} + +func (x *HeaderResponse) ProtoReflect() protoreflect.Message { + mi := &file_headers_headers_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeaderResponse.ProtoReflect.Descriptor instead. +func (*HeaderResponse) Descriptor() ([]byte, []int) { + return file_headers_headers_proto_rawDescGZIP(), []int{1} +} + +func (x *HeaderResponse) GetHeaders() []string { + if x != nil { + return x.Headers + } + return nil +} + +var File_headers_headers_proto protoreflect.FileDescriptor + +var file_headers_headers_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, + 0x0f, 0x0a, 0x0d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x2a, 0x0a, 0x0e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x32, 0x4a, 0x0a, 0x0d, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, + 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_headers_headers_proto_rawDescOnce sync.Once + file_headers_headers_proto_rawDescData = file_headers_headers_proto_rawDesc +) + +func file_headers_headers_proto_rawDescGZIP() []byte { + file_headers_headers_proto_rawDescOnce.Do(func() { + file_headers_headers_proto_rawDescData = protoimpl.X.CompressGZIP(file_headers_headers_proto_rawDescData) + }) + return file_headers_headers_proto_rawDescData +} + +var file_headers_headers_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_headers_headers_proto_goTypes = []interface{}{ + (*HeaderRequest)(nil), // 0: header.headerRequest + (*HeaderResponse)(nil), // 1: header.headerResponse +} +var file_headers_headers_proto_depIdxs = []int32{ + 0, // 0: header.HeaderService.Header:input_type -> header.headerRequest + 1, // 1: header.HeaderService.Header:output_type -> header.headerResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_headers_headers_proto_init() } +func file_headers_headers_proto_init() { + if File_headers_headers_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_headers_headers_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeaderRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_headers_headers_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeaderResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_headers_headers_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_headers_headers_proto_goTypes, + DependencyIndexes: file_headers_headers_proto_depIdxs, + MessageInfos: file_headers_headers_proto_msgTypes, + }.Build() + File_headers_headers_proto = out.File + file_headers_headers_proto_rawDesc = nil + file_headers_headers_proto_goTypes = nil + file_headers_headers_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/headers/headers.proto b/pkg/api/grpc/headers/headers.proto new file mode 100644 index 000000000..571e62d67 --- /dev/null +++ b/pkg/api/grpc/headers/headers.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option go_package = "./header"; + +package header; + +message HeaderRequest {} + +message HeaderResponse { + repeated string headers = 1; +} + +service HeaderService { + rpc Header(HeaderRequest) returns (HeaderResponse) {} +} diff --git a/pkg/api/grpc/headers/headers_grpc.pb.go b/pkg/api/grpc/headers/headers_grpc.pb.go new file mode 100644 index 000000000..39f2a3be9 --- /dev/null +++ b/pkg/api/grpc/headers/headers_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: headers/headers.proto + +package header + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// HeaderServiceClient is the client API for HeaderService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HeaderServiceClient interface { + Header(ctx context.Context, in *HeaderRequest, opts ...grpc.CallOption) (*HeaderResponse, error) +} + +type headerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewHeaderServiceClient(cc grpc.ClientConnInterface) HeaderServiceClient { + return &headerServiceClient{cc} +} + +func (c *headerServiceClient) Header(ctx context.Context, in *HeaderRequest, opts ...grpc.CallOption) (*HeaderResponse, error) { + out := new(HeaderResponse) + err := c.cc.Invoke(ctx, "/header.HeaderService/Header", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HeaderServiceServer is the server API for HeaderService service. +// All implementations must embed UnimplementedHeaderServiceServer +// for forward compatibility +type HeaderServiceServer interface { + Header(context.Context, *HeaderRequest) (*HeaderResponse, error) + mustEmbedUnimplementedHeaderServiceServer() +} + +// UnimplementedHeaderServiceServer must be embedded to have forward compatible implementations. +type UnimplementedHeaderServiceServer struct { +} + +func (UnimplementedHeaderServiceServer) Header(context.Context, *HeaderRequest) (*HeaderResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Header not implemented") +} +func (UnimplementedHeaderServiceServer) mustEmbedUnimplementedHeaderServiceServer() {} + +// UnsafeHeaderServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HeaderServiceServer will +// result in compilation errors. +type UnsafeHeaderServiceServer interface { + mustEmbedUnimplementedHeaderServiceServer() +} + +func RegisterHeaderServiceServer(s grpc.ServiceRegistrar, srv HeaderServiceServer) { + s.RegisterService(&HeaderService_ServiceDesc, srv) +} + +func _HeaderService_Header_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HeaderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HeaderServiceServer).Header(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/header.HeaderService/Header", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HeaderServiceServer).Header(ctx, req.(*HeaderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// HeaderService_ServiceDesc is the grpc.ServiceDesc for HeaderService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var HeaderService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "header.HeaderService", + HandlerType: (*HeaderServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Header", + Handler: _HeaderService_Header_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "headers/headers.proto", +} diff --git a/pkg/api/grpc/headers_test.go b/pkg/api/grpc/headers_test.go new file mode 100644 index 000000000..fe8cec2f0 --- /dev/null +++ b/pkg/api/grpc/headers_test.go @@ -0,0 +1,69 @@ +package grpc + +import ( + "context" + "log" + "net" + "regexp" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/headers" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcHeader(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + header.RegisterHeaderServiceServer(srv, &HeaderServer{}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + conn, err := grpc.DialContext(context.Background(), "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + headers := metadata.New(map[string]string{ + "X-Test": "testing", + }) + + ctx := metadata.NewOutgoingContext(context.Background(), headers) + + client := header.NewHeaderServiceClient(conn) + res, err := client.Header(ctx, &header.HeaderRequest{}) + + if _, ok := status.FromError(err); !ok { + t.Errorf("Header returned type %T, want %T", err, status.Error) + } + + expected := ".*testing.*" + r := regexp.MustCompile(expected) + if !r.MatchString(res.String()) { + t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", + res, expected) + } +} diff --git a/pkg/api/grpc/info.go b/pkg/api/grpc/info.go new file mode 100644 index 000000000..27552f188 --- /dev/null +++ b/pkg/api/grpc/info.go @@ -0,0 +1,70 @@ +package grpc + +import ( + "context" + "go.uber.org/zap" + "log" + "runtime" + "strconv" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/info" + "github.com/stefanprodan/podinfo/pkg/version" +) + +type infoServer struct { + pb.UnimplementedInfoServiceServer + config *Config + logger *zap.Logger +} + +func (s *infoServer) Info(ctx context.Context, message *pb.InfoRequest) (*pb.InfoResponse, error) { + + defer func() { + if r := recover(); r != nil { + log.Println("Recovered from panic:", r) + } + }() + + data := RuntimeResponse{ + Hostname: s.config.Hostname, + Version: version.VERSION, + Revision: version.REVISION, + Color: s.config.UIColor, + Logo: s.config.UILogo, + Message: s.config.UIMessage, + Goos: runtime.GOOS, + Goarch: runtime.GOARCH, + Runtime: runtime.Version(), + Numgoroutine: strconv.FormatInt(int64(runtime.NumGoroutine()), 10), + Numcpu: strconv.FormatInt(int64(runtime.NumCPU()), 10), + } + + return &pb.InfoResponse{ + Hostname: data.Hostname, + Version: data.Version, + Revision: data.Revision, + Color: data.Color, + Logo: data.Logo, + Message: data.Message, + Goos: data.Goos, + Goarch: data.Goarch, + Runtime: data.Runtime, + Numgoroutine: data.Numgoroutine, + Numcpu: data.Numcpu, + }, nil + +} + +type RuntimeResponse struct { + Hostname string `json:"hostname"` + Version string `json:"version"` + Revision string `json:"revision"` + Color string `json:"color"` + Logo string `json:"logo"` + Message string `json:"message"` + Goos string `json:"goos"` + Goarch string `json:"goarch"` + Runtime string `json:"runtime"` + Numgoroutine string `json:"num_goroutine"` + Numcpu string `json:"num_cpu"` +} diff --git a/pkg/api/grpc/info/info.pb.go b/pkg/api/grpc/info/info.pb.go new file mode 100644 index 000000000..6f40e069e --- /dev/null +++ b/pkg/api/grpc/info/info.pb.go @@ -0,0 +1,296 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: info/info.proto + +package info + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type InfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *InfoRequest) Reset() { + *x = InfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_info_info_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InfoRequest) ProtoMessage() {} + +func (x *InfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_info_info_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InfoRequest.ProtoReflect.Descriptor instead. +func (*InfoRequest) Descriptor() ([]byte, []int) { + return file_info_info_proto_rawDescGZIP(), []int{0} +} + +type InfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Revision string `protobuf:"bytes,3,opt,name=revision,proto3" json:"revision,omitempty"` + Color string `protobuf:"bytes,4,opt,name=color,proto3" json:"color,omitempty"` + Logo string `protobuf:"bytes,5,opt,name=logo,proto3" json:"logo,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` + Goos string `protobuf:"bytes,7,opt,name=goos,proto3" json:"goos,omitempty"` + Goarch string `protobuf:"bytes,8,opt,name=goarch,proto3" json:"goarch,omitempty"` + Runtime string `protobuf:"bytes,9,opt,name=runtime,proto3" json:"runtime,omitempty"` + Numgoroutine string `protobuf:"bytes,10,opt,name=numgoroutine,proto3" json:"numgoroutine,omitempty"` + Numcpu string `protobuf:"bytes,11,opt,name=numcpu,proto3" json:"numcpu,omitempty"` +} + +func (x *InfoResponse) Reset() { + *x = InfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_info_info_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InfoResponse) ProtoMessage() {} + +func (x *InfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_info_info_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InfoResponse.ProtoReflect.Descriptor instead. +func (*InfoResponse) Descriptor() ([]byte, []int) { + return file_info_info_proto_rawDescGZIP(), []int{1} +} + +func (x *InfoResponse) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *InfoResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *InfoResponse) GetRevision() string { + if x != nil { + return x.Revision + } + return "" +} + +func (x *InfoResponse) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *InfoResponse) GetLogo() string { + if x != nil { + return x.Logo + } + return "" +} + +func (x *InfoResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *InfoResponse) GetGoos() string { + if x != nil { + return x.Goos + } + return "" +} + +func (x *InfoResponse) GetGoarch() string { + if x != nil { + return x.Goarch + } + return "" +} + +func (x *InfoResponse) GetRuntime() string { + if x != nil { + return x.Runtime + } + return "" +} + +func (x *InfoResponse) GetNumgoroutine() string { + if x != nil { + return x.Numgoroutine + } + return "" +} + +func (x *InfoResponse) GetNumcpu() string { + if x != nil { + return x.Numcpu + } + return "" +} + +var File_info_info_proto protoreflect.FileDescriptor + +var file_info_info_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x69, 0x6e, 0x66, 0x6f, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x0d, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa6, 0x02, 0x0a, 0x0c, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, + 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, + 0x12, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, + 0x6f, 0x67, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x67, 0x6f, 0x6f, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x6f, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x6f, 0x61, 0x72, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x67, 0x6f, 0x61, 0x72, 0x63, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x75, 0x6e, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x75, 0x6d, 0x67, 0x6f, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x67, 0x6f, + 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x63, 0x70, + 0x75, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x63, 0x70, 0x75, 0x32, + 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, + 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x11, 0x2e, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x69, 0x6e, 0x66, 0x6f, + 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_info_info_proto_rawDescOnce sync.Once + file_info_info_proto_rawDescData = file_info_info_proto_rawDesc +) + +func file_info_info_proto_rawDescGZIP() []byte { + file_info_info_proto_rawDescOnce.Do(func() { + file_info_info_proto_rawDescData = protoimpl.X.CompressGZIP(file_info_info_proto_rawDescData) + }) + return file_info_info_proto_rawDescData +} + +var file_info_info_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_info_info_proto_goTypes = []interface{}{ + (*InfoRequest)(nil), // 0: info.InfoRequest + (*InfoResponse)(nil), // 1: info.InfoResponse +} +var file_info_info_proto_depIdxs = []int32{ + 0, // 0: info.InfoService.Info:input_type -> info.InfoRequest + 1, // 1: info.InfoService.Info:output_type -> info.InfoResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_info_info_proto_init() } +func file_info_info_proto_init() { + if File_info_info_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_info_info_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_info_info_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_info_info_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_info_info_proto_goTypes, + DependencyIndexes: file_info_info_proto_depIdxs, + MessageInfos: file_info_info_proto_msgTypes, + }.Build() + File_info_info_proto = out.File + file_info_info_proto_rawDesc = nil + file_info_info_proto_goTypes = nil + file_info_info_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/info/info.proto b/pkg/api/grpc/info/info.proto new file mode 100644 index 000000000..76ae2575f --- /dev/null +++ b/pkg/api/grpc/info/info.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +option go_package = "./info"; + +package info; + +message InfoRequest {} + +message InfoResponse { + string hostname = 1; + string version = 2; + string revision = 3; + string color = 4 ; + string logo = 5 ; + string message = 6 ; + string goos = 7; + string goarch = 8; + string runtime = 9; + string numgoroutine = 10; + string numcpu = 11; +} + +service InfoService { + rpc Info (InfoRequest) returns (InfoResponse) {} +} \ No newline at end of file diff --git a/pkg/api/grpc/info/info_grpc.pb.go b/pkg/api/grpc/info/info_grpc.pb.go new file mode 100644 index 000000000..97598a0d4 --- /dev/null +++ b/pkg/api/grpc/info/info_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: info/info.proto + +package info + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// InfoServiceClient is the client API for InfoService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type InfoServiceClient interface { + Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error) +} + +type infoServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewInfoServiceClient(cc grpc.ClientConnInterface) InfoServiceClient { + return &infoServiceClient{cc} +} + +func (c *infoServiceClient) Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error) { + out := new(InfoResponse) + err := c.cc.Invoke(ctx, "/info.InfoService/Info", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// InfoServiceServer is the server API for InfoService service. +// All implementations must embed UnimplementedInfoServiceServer +// for forward compatibility +type InfoServiceServer interface { + Info(context.Context, *InfoRequest) (*InfoResponse, error) + mustEmbedUnimplementedInfoServiceServer() +} + +// UnimplementedInfoServiceServer must be embedded to have forward compatible implementations. +type UnimplementedInfoServiceServer struct { +} + +func (UnimplementedInfoServiceServer) Info(context.Context, *InfoRequest) (*InfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Info not implemented") +} +func (UnimplementedInfoServiceServer) mustEmbedUnimplementedInfoServiceServer() {} + +// UnsafeInfoServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to InfoServiceServer will +// result in compilation errors. +type UnsafeInfoServiceServer interface { + mustEmbedUnimplementedInfoServiceServer() +} + +func RegisterInfoServiceServer(s grpc.ServiceRegistrar, srv InfoServiceServer) { + s.RegisterService(&InfoService_ServiceDesc, srv) +} + +func _InfoService_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InfoServiceServer).Info(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/info.InfoService/Info", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InfoServiceServer).Info(ctx, req.(*InfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// InfoService_ServiceDesc is the grpc.ServiceDesc for InfoService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var InfoService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "info.InfoService", + HandlerType: (*InfoServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Info", + Handler: _InfoService_Info_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "info/info.proto", +} diff --git a/pkg/api/grpc/info_test.go b/pkg/api/grpc/info_test.go new file mode 100644 index 000000000..342d617c0 --- /dev/null +++ b/pkg/api/grpc/info_test.go @@ -0,0 +1,65 @@ +package grpc + +import ( + "context" + "log" + "net" + "regexp" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/info" + "google.golang.org/grpc" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcInfo(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + s := NewMockGrpcServer() + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + info.RegisterInfoServiceServer(srv, &infoServer{config: s.config}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := info.NewInfoServiceClient(conn) + res, err := client.Info(context.Background(), &info.InfoRequest{}) + + if _, ok := status.FromError(err); !ok { + t.Errorf("Info returned type %T, want %T", err, status.Error) + } + + expected := ".*color.*blue.*" + r := regexp.MustCompile(expected) + if !r.MatchString(res.String()) { + t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", + res.Color, expected) + } +} diff --git a/pkg/api/grpc/mock_grpc.go b/pkg/api/grpc/mock_grpc.go index f05827ed7..c596d8594 100644 --- a/pkg/api/grpc/mock_grpc.go +++ b/pkg/api/grpc/mock_grpc.go @@ -4,20 +4,19 @@ import ( "go.uber.org/zap" ) - func NewMockGrpcServer() *Server { config := &Config{ - Port: 9999, + Port: 9999, // ServerShutdownTimeout: 5 * time.Second, // HttpServerTimeout: 30 * time.Second, - BackendURL: []string{}, - ConfigPath: "/config", - DataPath: "/data", + BackendURL: []string{}, + ConfigPath: "/config", + DataPath: "/data", // HttpClientTimeout: 30 * time.Second, - UIColor: "blue", - UIPath: ".ui", - UIMessage: "Greetings", - Hostname: "localhost", + UIColor: "blue", + UIPath: ".ui", + UIMessage: "Greetings", + Hostname: "localhost", } logger, _ := zap.NewDevelopment() @@ -28,4 +27,4 @@ func NewMockGrpcServer() *Server { config: config, //tracer: trace.NewNoopTracerProvider().Tracer("mock"), } -} \ No newline at end of file +} diff --git a/pkg/api/grpc/panic.go b/pkg/api/grpc/panic.go index b47b82aa2..b43fcd7d9 100644 --- a/pkg/api/grpc/panic.go +++ b/pkg/api/grpc/panic.go @@ -13,7 +13,6 @@ type PanicServer struct { pb.UnimplementedPanicServiceServer config *Config logger *zap.Logger - } func (s *PanicServer) Panic(ctx context.Context, req *pb.PanicRequest) (*pb.PanicResponse, error) { @@ -21,4 +20,3 @@ func (s *PanicServer) Panic(ctx context.Context, req *pb.PanicRequest) (*pb.Pani os.Exit(225) return &pb.PanicResponse{}, nil } - diff --git a/pkg/api/grpc/server.go b/pkg/api/grpc/server.go index 97592b58c..24c7d5330 100644 --- a/pkg/api/grpc/server.go +++ b/pkg/api/grpc/server.go @@ -12,11 +12,16 @@ import ( "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" + "github.com/stefanprodan/podinfo/pkg/api/grpc/delay" + "github.com/stefanprodan/podinfo/pkg/api/grpc/env" + header "github.com/stefanprodan/podinfo/pkg/api/grpc/headers" + "github.com/stefanprodan/podinfo/pkg/api/grpc/info" "github.com/stefanprodan/podinfo/pkg/api/grpc/panic" + "github.com/stefanprodan/podinfo/pkg/api/grpc/status" + "github.com/stefanprodan/podinfo/pkg/api/grpc/token" "github.com/stefanprodan/podinfo/pkg/api/grpc/version" ) - type Server struct { logger *zap.Logger config *Config @@ -26,31 +31,29 @@ type Config struct { Port int `mapstructure:"grpc-port"` ServiceName string `mapstructure:"grpc-service-name"` - - BackendURL []string `mapstructure:"backend-url"` - UILogo string `mapstructure:"ui-logo"` - UIMessage string `mapstructure:"ui-message"` - UIColor string `mapstructure:"ui-color"` - UIPath string `mapstructure:"ui-path"` - DataPath string `mapstructure:"data-path"` - ConfigPath string `mapstructure:"config-path"` - CertPath string `mapstructure:"cert-path"` - Host string `mapstructure:"host"` + BackendURL []string `mapstructure:"backend-url"` + UILogo string `mapstructure:"ui-logo"` + UIMessage string `mapstructure:"ui-message"` + UIColor string `mapstructure:"ui-color"` + UIPath string `mapstructure:"ui-path"` + DataPath string `mapstructure:"data-path"` + ConfigPath string `mapstructure:"config-path"` + CertPath string `mapstructure:"cert-path"` + Host string `mapstructure:"host"` //Port string `mapstructure:"port"` - SecurePort string `mapstructure:"secure-port"` - PortMetrics int `mapstructure:"port-metrics"` - Hostname string `mapstructure:"hostname"` - H2C bool `mapstructure:"h2c"` - RandomDelay bool `mapstructure:"random-delay"` - RandomDelayUnit string `mapstructure:"random-delay-unit"` - RandomDelayMin int `mapstructure:"random-delay-min"` - RandomDelayMax int `mapstructure:"random-delay-max"` - RandomError bool `mapstructure:"random-error"` - Unhealthy bool `mapstructure:"unhealthy"` - Unready bool `mapstructure:"unready"` - JWTSecret string `mapstructure:"jwt-secret"` - CacheServer string `mapstructure:"cache-server"` - + SecurePort string `mapstructure:"secure-port"` + PortMetrics int `mapstructure:"port-metrics"` + Hostname string `mapstructure:"hostname"` + H2C bool `mapstructure:"h2c"` + RandomDelay bool `mapstructure:"random-delay"` + RandomDelayUnit string `mapstructure:"random-delay-unit"` + RandomDelayMin int `mapstructure:"random-delay-min"` + RandomDelayMax int `mapstructure:"random-delay-max"` + RandomError bool `mapstructure:"random-error"` + Unhealthy bool `mapstructure:"unhealthy"` + Unready bool `mapstructure:"unready"` + JWTSecret string `mapstructure:"jwt-secret"` + CacheServer string `mapstructure:"cache-server"` } func NewServer(config *Config, logger *zap.Logger) (*Server, error) { @@ -62,7 +65,6 @@ func NewServer(config *Config, logger *zap.Logger) (*Server, error) { return srv, nil } - func (s *Server) ListenAndServe() *grpc.Server { listener, err := net.Listen("tcp", fmt.Sprintf(":%v", s.config.Port)) if err != nil { @@ -72,12 +74,16 @@ func (s *Server) ListenAndServe() *grpc.Server { srv := grpc.NewServer() server := health.NewServer() - // Register grpc apis for reflection echo.RegisterEchoServiceServer(srv, &echoServer{config: s.config, logger: s.logger}) - version.RegisterVersionServiceServer(srv, &VersionServer{config: s.config, logger: s.logger}) panic.RegisterPanicServiceServer(srv, &PanicServer{config: s.config, logger: s.logger}) + delay.RegisterDelayServiceServer(srv, &DelayServer{config: s.config, logger: s.logger}) + header.RegisterHeaderServiceServer(srv, &HeaderServer{config: s.config, logger: s.logger}) + info.RegisterInfoServiceServer(srv, &infoServer{config: s.config}) + status.RegisterStatusServiceServer(srv, &StatusServer{config: s.config, logger: s.logger}) + token.RegisterTokenServiceServer(srv, &TokenServer{config: s.config, logger: s.logger}) + env.RegisterEnvServiceServer(srv, &EnvServer{config: s.config, logger: s.logger}) reflection.Register(srv) grpc_health_v1.RegisterHealthServer(srv, server) diff --git a/pkg/api/grpc/status.go b/pkg/api/grpc/status.go new file mode 100644 index 000000000..817b8f572 --- /dev/null +++ b/pkg/api/grpc/status.go @@ -0,0 +1,47 @@ +package grpc + +import ( + "context" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/status" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type StatusServer struct { + pb.UnimplementedStatusServiceServer + config *Config + logger *zap.Logger +} + +func (s *StatusServer) Status(ctx context.Context, req *pb.StatusRequest) (*pb.StatusResponse, error) { + reqCode := req.GetCode() + + grpcCodes := map[string]codes.Code{ + "Ok": codes.OK, + "Canceled": codes.Canceled, + "Unknown": codes.Unknown, + "InvalidArgument": codes.InvalidArgument, + "DeadlineExceeded": codes.DeadlineExceeded, + "NotFound": codes.NotFound, + "AlreadyExists": codes.AlreadyExists, + "PermissionDenied": codes.PermissionDenied, + "ResourceExhausted": codes.ResourceExhausted, + "FailedPrecondition": codes.FailedPrecondition, + "Aborted": codes.Aborted, + "OutOfRange": codes.OutOfRange, + "Unimplemented": codes.Unimplemented, + "Internal": codes.Internal, + "Unavailable": codes.Unavailable, + "DataLoss": codes.DataLoss, + "Unauthenticated": codes.Unauthenticated, + } + + code, ok := grpcCodes[reqCode] + if !ok { + return nil, status.Error(codes.Unknown, "Unknown status code for more information check https://chromium.googlesource.com/external/github.com/grpc/grpc/+/refs/tags/v1.21.4-pre1/doc/statuscodes.md") + } + + return &pb.StatusResponse{Status: reqCode}, status.Error(code, "") +} diff --git a/pkg/api/grpc/status/status.pb.go b/pkg/api/grpc/status/status.pb.go new file mode 100644 index 000000000..80f907b5f --- /dev/null +++ b/pkg/api/grpc/status/status.pb.go @@ -0,0 +1,211 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: status/status.proto + +package status + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *StatusRequest) Reset() { + *x = StatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_status_status_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusRequest) ProtoMessage() {} + +func (x *StatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_status_status_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusRequest.ProtoReflect.Descriptor instead. +func (*StatusRequest) Descriptor() ([]byte, []int) { + return file_status_status_proto_rawDescGZIP(), []int{0} +} + +func (x *StatusRequest) GetCode() string { + if x != nil { + return x.Code + } + return "" +} + +type StatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *StatusResponse) Reset() { + *x = StatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_status_status_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResponse) ProtoMessage() {} + +func (x *StatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_status_status_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. +func (*StatusResponse) Descriptor() ([]byte, []int) { + return file_status_status_proto_rawDescGZIP(), []int{1} +} + +func (x *StatusResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +var File_status_status_proto protoreflect.FileDescriptor + +var file_status_status_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x23, 0x0a, + 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x22, 0x28, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0x4a, 0x0a, 0x0d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_status_status_proto_rawDescOnce sync.Once + file_status_status_proto_rawDescData = file_status_status_proto_rawDesc +) + +func file_status_status_proto_rawDescGZIP() []byte { + file_status_status_proto_rawDescOnce.Do(func() { + file_status_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_status_status_proto_rawDescData) + }) + return file_status_status_proto_rawDescData +} + +var file_status_status_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_status_status_proto_goTypes = []interface{}{ + (*StatusRequest)(nil), // 0: status.StatusRequest + (*StatusResponse)(nil), // 1: status.StatusResponse +} +var file_status_status_proto_depIdxs = []int32{ + 0, // 0: status.StatusService.Status:input_type -> status.StatusRequest + 1, // 1: status.StatusService.Status:output_type -> status.StatusResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_status_status_proto_init() } +func file_status_status_proto_init() { + if File_status_status_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_status_status_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_status_status_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_status_status_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_status_status_proto_goTypes, + DependencyIndexes: file_status_status_proto_depIdxs, + MessageInfos: file_status_status_proto_msgTypes, + }.Build() + File_status_status_proto = out.File + file_status_status_proto_rawDesc = nil + file_status_status_proto_goTypes = nil + file_status_status_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/status/status.proto b/pkg/api/grpc/status/status.proto new file mode 100644 index 000000000..e1eb850e5 --- /dev/null +++ b/pkg/api/grpc/status/status.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option go_package = "./status"; + +package status; + +// The greeting service definition. +service StatusService { + rpc Status (StatusRequest) returns (StatusResponse) {} +} + +message StatusRequest { + string code = 1; +} + +message StatusResponse { + string status = 1; +} diff --git a/pkg/api/grpc/status/status_grpc.pb.go b/pkg/api/grpc/status/status_grpc.pb.go new file mode 100644 index 000000000..5f957fc45 --- /dev/null +++ b/pkg/api/grpc/status/status_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: status/status.proto + +package status + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// StatusServiceClient is the client API for StatusService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StatusServiceClient interface { + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type statusServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStatusServiceClient(cc grpc.ClientConnInterface) StatusServiceClient { + return &statusServiceClient{cc} +} + +func (c *statusServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/status.StatusService/Status", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StatusServiceServer is the server API for StatusService service. +// All implementations must embed UnimplementedStatusServiceServer +// for forward compatibility +type StatusServiceServer interface { + Status(context.Context, *StatusRequest) (*StatusResponse, error) + mustEmbedUnimplementedStatusServiceServer() +} + +// UnimplementedStatusServiceServer must be embedded to have forward compatible implementations. +type UnimplementedStatusServiceServer struct { +} + +func (UnimplementedStatusServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") +} +func (UnimplementedStatusServiceServer) mustEmbedUnimplementedStatusServiceServer() {} + +// UnsafeStatusServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StatusServiceServer will +// result in compilation errors. +type UnsafeStatusServiceServer interface { + mustEmbedUnimplementedStatusServiceServer() +} + +func RegisterStatusServiceServer(s grpc.ServiceRegistrar, srv StatusServiceServer) { + s.RegisterService(&StatusService_ServiceDesc, srv) +} + +func _StatusService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatusServiceServer).Status(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/status.StatusService/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatusServiceServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// StatusService_ServiceDesc is the grpc.ServiceDesc for StatusService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StatusService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "status.StatusService", + HandlerType: (*StatusServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Status", + Handler: _StatusService_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "status/status.proto", +} diff --git a/pkg/api/grpc/status_test.go b/pkg/api/grpc/status_test.go new file mode 100644 index 000000000..65eca25e5 --- /dev/null +++ b/pkg/api/grpc/status_test.go @@ -0,0 +1,124 @@ +package grpc + +import ( + "context" + "fmt" + "log" + "net" + "regexp" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + st "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcStatusError(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + status.RegisterStatusServiceServer(srv, &StatusServer{}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := status.NewStatusServiceClient(conn) + + res, err := client.Status(context.Background(), &status.StatusRequest{Code: "NotFound"}) + + if err != nil { + if e, ok := st.FromError(err); ok { + if e.Code() != codes.NotFound { + if res != nil { + fmt.Printf("res %v\n", res) + } + t.Errorf("Status returned %s, want %s", fmt.Sprint(e.Code()), fmt.Sprint(codes.Aborted)) + } + } + } + +} + +func TestGrpcStatusOk(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + + status.RegisterStatusServiceServer(srv, &StatusServer{}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := status.NewStatusServiceClient(conn) + + res, err := client.Status(context.Background(), &status.StatusRequest{Code: "Ok"}) + + if err != nil { + if e, ok := st.FromError(err); ok { + t.Errorf("Status returned %s, want %s", fmt.Sprint(e.Code()), fmt.Sprint(codes.OK)) + } + } + + expected := ".*Ok.*" + r := regexp.MustCompile(expected) + if res != nil { + if !r.MatchString(res.Status) { + t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", + res, expected) + } + } + +} diff --git a/pkg/api/grpc/token.go b/pkg/api/grpc/token.go new file mode 100644 index 000000000..b5a7c52e0 --- /dev/null +++ b/pkg/api/grpc/token.go @@ -0,0 +1,103 @@ +package grpc + +import ( + "context" + "strings" + "time" + + "github.com/golang-jwt/jwt/v4" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + pb "github.com/stefanprodan/podinfo/pkg/api/grpc/token" +) + +type TokenServer struct { + pb.UnimplementedTokenServiceServer + config *Config + logger *zap.Logger +} + +type jwtCustomClaims struct { + Name string `json:"name"` + jwt.StandardClaims +} + +func (s *TokenServer) TokenGenerate(ctx context.Context, req *pb.TokenRequest) (*pb.TokenResponse, error) { + + user := "anonymous" + expiresAt := time.Now().Add(time.Minute * 1).Unix() + + claims := &jwtCustomClaims{ + user, + jwt.StandardClaims{ + Issuer: "podinfo", + ExpiresAt: expiresAt, + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + t, err := token.SignedString([]byte(s.config.JWTSecret)) + + if err != nil { + s.logger.Error("Failed to generate token", zap.Error(err)) + return &pb.TokenResponse{}, err + } + + var result = pb.TokenResponse{ + Token: t, + ExpiresAt: time.Unix(claims.StandardClaims.ExpiresAt, 0).String(), + Message: "Token generated successfully", + } + + return &result, nil +} + +func (s *TokenServer) TokenValidate(ctx context.Context, req *pb.TokenRequest) (*pb.TokenResponse, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata") + } + + authorization := md.Get("authorization") + + if len(authorization) == 0 { + return nil, status.Errorf(codes.Unauthenticated, "Authorization token not found in metadata") + } + + token := strings.TrimSpace(strings.TrimPrefix(authorization[0], "Bearer")) + + claims := jwtCustomClaims{} + + parsed_token, err := jwt.ParseWithClaims(token, &claims, func(parsed_token *jwt.Token) (interface{}, error) { + if _, ok := parsed_token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, status.Errorf(codes.Canceled, "invalid signing method") + } + return []byte(s.config.JWTSecret), nil + }) + if err != nil { + if strings.Contains(err.Error(), "token is expired") || strings.Contains(err.Error(), "signature is invalid") { + return &pb.TokenResponse{ + Message: err.Error(), + }, nil + } + return nil, status.Errorf(codes.Unauthenticated, "Unable to parse token") + + } + + if parsed_token.Valid { + if claims.StandardClaims.Issuer != "podinfo" { + return nil, status.Errorf(codes.OK, "Invalid issuer") + } else { + var result = pb.TokenResponse{ + Token: claims.Name, + ExpiresAt: time.Unix(claims.StandardClaims.ExpiresAt, 0).String(), + } + return &result, nil + } + } else { + return nil, status.Errorf(codes.Unauthenticated, "Unauthenticated") + } +} diff --git a/pkg/api/grpc/token/token.pb.go b/pkg/api/grpc/token/token.pb.go new file mode 100644 index 000000000..9633657ff --- /dev/null +++ b/pkg/api/grpc/token/token.pb.go @@ -0,0 +1,226 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.25.0 +// source: token/token.proto + +package token + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TokenRequest) Reset() { + *x = TokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_token_token_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenRequest) ProtoMessage() {} + +func (x *TokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_token_token_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenRequest.ProtoReflect.Descriptor instead. +func (*TokenRequest) Descriptor() ([]byte, []int) { + return file_token_token_proto_rawDescGZIP(), []int{0} +} + +type TokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + ExpiresAt string `protobuf:"bytes,2,opt,name=expiresAt,proto3" json:"expiresAt,omitempty"` + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *TokenResponse) Reset() { + *x = TokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_token_token_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenResponse) ProtoMessage() {} + +func (x *TokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_token_token_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenResponse.ProtoReflect.Descriptor instead. +func (*TokenResponse) Descriptor() ([]byte, []int) { + return file_token_token_proto_rawDescGZIP(), []int{1} +} + +func (x *TokenResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *TokenResponse) GetExpiresAt() string { + if x != nil { + return x.ExpiresAt + } + return "" +} + +func (x *TokenResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_token_token_proto protoreflect.FileDescriptor + +var file_token_token_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x0e, 0x0a, 0x0c, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5d, 0x0a, 0x0d, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x8a, 0x01, 0x0a, 0x0c, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3c, 0x0a, 0x0d, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x14, 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x13, 0x2e, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_token_token_proto_rawDescOnce sync.Once + file_token_token_proto_rawDescData = file_token_token_proto_rawDesc +) + +func file_token_token_proto_rawDescGZIP() []byte { + file_token_token_proto_rawDescOnce.Do(func() { + file_token_token_proto_rawDescData = protoimpl.X.CompressGZIP(file_token_token_proto_rawDescData) + }) + return file_token_token_proto_rawDescData +} + +var file_token_token_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_token_token_proto_goTypes = []interface{}{ + (*TokenRequest)(nil), // 0: token.TokenRequest + (*TokenResponse)(nil), // 1: token.TokenResponse +} +var file_token_token_proto_depIdxs = []int32{ + 0, // 0: token.TokenService.TokenGenerate:input_type -> token.TokenRequest + 0, // 1: token.TokenService.TokenValidate:input_type -> token.TokenRequest + 1, // 2: token.TokenService.TokenGenerate:output_type -> token.TokenResponse + 1, // 3: token.TokenService.TokenValidate:output_type -> token.TokenResponse + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_token_token_proto_init() } +func file_token_token_proto_init() { + if File_token_token_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_token_token_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_token_token_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_token_token_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_token_token_proto_goTypes, + DependencyIndexes: file_token_token_proto_depIdxs, + MessageInfos: file_token_token_proto_msgTypes, + }.Build() + File_token_token_proto = out.File + file_token_token_proto_rawDesc = nil + file_token_token_proto_goTypes = nil + file_token_token_proto_depIdxs = nil +} diff --git a/pkg/api/grpc/token/token.proto b/pkg/api/grpc/token/token.proto new file mode 100644 index 000000000..1e61fcccb --- /dev/null +++ b/pkg/api/grpc/token/token.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +option go_package = "./token"; + +package token; + + +// The greeting service definition. +service TokenService { + rpc TokenGenerate (TokenRequest) returns (TokenResponse) {} + rpc TokenValidate (TokenRequest) returns (TokenResponse) {} +} + +message TokenRequest {} + +message TokenResponse { + string token = 1; + string expiresAt = 2; + string message = 3; +} \ No newline at end of file diff --git a/pkg/api/grpc/token/token_grpc.pb.go b/pkg/api/grpc/token/token_grpc.pb.go new file mode 100644 index 000000000..b3719ef2d --- /dev/null +++ b/pkg/api/grpc/token/token_grpc.pb.go @@ -0,0 +1,141 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.25.0 +// source: token/token.proto + +package token + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// TokenServiceClient is the client API for TokenService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TokenServiceClient interface { + TokenGenerate(ctx context.Context, in *TokenRequest, opts ...grpc.CallOption) (*TokenResponse, error) + TokenValidate(ctx context.Context, in *TokenRequest, opts ...grpc.CallOption) (*TokenResponse, error) +} + +type tokenServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTokenServiceClient(cc grpc.ClientConnInterface) TokenServiceClient { + return &tokenServiceClient{cc} +} + +func (c *tokenServiceClient) TokenGenerate(ctx context.Context, in *TokenRequest, opts ...grpc.CallOption) (*TokenResponse, error) { + out := new(TokenResponse) + err := c.cc.Invoke(ctx, "/token.TokenService/TokenGenerate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tokenServiceClient) TokenValidate(ctx context.Context, in *TokenRequest, opts ...grpc.CallOption) (*TokenResponse, error) { + out := new(TokenResponse) + err := c.cc.Invoke(ctx, "/token.TokenService/TokenValidate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TokenServiceServer is the server API for TokenService service. +// All implementations must embed UnimplementedTokenServiceServer +// for forward compatibility +type TokenServiceServer interface { + TokenGenerate(context.Context, *TokenRequest) (*TokenResponse, error) + TokenValidate(context.Context, *TokenRequest) (*TokenResponse, error) + mustEmbedUnimplementedTokenServiceServer() +} + +// UnimplementedTokenServiceServer must be embedded to have forward compatible implementations. +type UnimplementedTokenServiceServer struct { +} + +func (UnimplementedTokenServiceServer) TokenGenerate(context.Context, *TokenRequest) (*TokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TokenGenerate not implemented") +} +func (UnimplementedTokenServiceServer) TokenValidate(context.Context, *TokenRequest) (*TokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TokenValidate not implemented") +} +func (UnimplementedTokenServiceServer) mustEmbedUnimplementedTokenServiceServer() {} + +// UnsafeTokenServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TokenServiceServer will +// result in compilation errors. +type UnsafeTokenServiceServer interface { + mustEmbedUnimplementedTokenServiceServer() +} + +func RegisterTokenServiceServer(s grpc.ServiceRegistrar, srv TokenServiceServer) { + s.RegisterService(&TokenService_ServiceDesc, srv) +} + +func _TokenService_TokenGenerate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TokenServiceServer).TokenGenerate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/token.TokenService/TokenGenerate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TokenServiceServer).TokenGenerate(ctx, req.(*TokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TokenService_TokenValidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TokenServiceServer).TokenValidate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/token.TokenService/TokenValidate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TokenServiceServer).TokenValidate(ctx, req.(*TokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// TokenService_ServiceDesc is the grpc.ServiceDesc for TokenService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TokenService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "token.TokenService", + HandlerType: (*TokenServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "TokenGenerate", + Handler: _TokenService_TokenGenerate_Handler, + }, + { + MethodName: "TokenValidate", + Handler: _TokenService_TokenValidate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "token/token.proto", +} diff --git a/pkg/api/grpc/token_test.go b/pkg/api/grpc/token_test.go new file mode 100644 index 000000000..78012cd2e --- /dev/null +++ b/pkg/api/grpc/token_test.go @@ -0,0 +1,66 @@ +package grpc + +import ( + "context" + "log" + "net" + "testing" + + "github.com/stefanprodan/podinfo/pkg/api/grpc/token" + "google.golang.org/grpc" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestGrpcToken(t *testing.T) { + + lis := bufconn.Listen(1024 * 1024) + t.Cleanup(func() { + lis.Close() + }) + + srv := grpc.NewServer() + t.Cleanup(func() { + srv.Stop() + }) + config := &Config{} + config.JWTSecret = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + token.RegisterTokenServiceServer(srv, &TokenServer{config: config}) + + go func() { + if err := srv.Serve(lis); err != nil { + log.Fatalf("srv.Serve %v", err) + } + }() + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + ctx := context.Background() + + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) + t.Cleanup(func() { + conn.Close() + }) + + if err != nil { + t.Fatalf("grpc.DialContext %v", err) + } + + client := token.NewTokenServiceClient(conn) + res, err := client.TokenGenerate(context.Background(), &token.TokenRequest{}) + + if _, ok := status.FromError(err); !ok { + t.Errorf("Token Handler returned type %T, want %T", err, status.Error) + } + + var token = token.TokenResponse{ + Token: res.Token, + ExpiresAt: res.ExpiresAt, + } + + if token.Token == "" { + t.Fatalf("Handler returned no token") + } +} diff --git a/pkg/api/grpc/version.go b/pkg/api/grpc/version.go index 8291e2e6b..258ff854a 100644 --- a/pkg/api/grpc/version.go +++ b/pkg/api/grpc/version.go @@ -12,10 +12,8 @@ type VersionServer struct { pb.UnimplementedVersionServiceServer config *Config logger *zap.Logger - } func (s *VersionServer) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { return &pb.VersionResponse{Version: version.VERSION, Commit: version.REVISION}, nil } - diff --git a/pkg/api/grpc/version_test.go b/pkg/api/grpc/version_test.go index 7cdbc21a3..ec38de8dc 100644 --- a/pkg/api/grpc/version_test.go +++ b/pkg/api/grpc/version_test.go @@ -17,14 +17,11 @@ import ( func TestGrpcVersion(t *testing.T) { - // Server initialization - // bufconn => uses in-memory connection instead of system network I/O - lis := bufconn.Listen(1024*1024) + lis := bufconn.Listen(1024 * 1024) t.Cleanup(func() { lis.Close() }) - srv := grpc.NewServer() t.Cleanup(func() { srv.Stop() @@ -32,19 +29,18 @@ func TestGrpcVersion(t *testing.T) { version.RegisterVersionServiceServer(srv, &VersionServer{}) - go func(){ + go func() { if err := srv.Serve(lis); err != nil { log.Fatalf("srv.Serve %v", err) } }() - // - Test - dialer := func(context.Context, string) (net.Conn, error){ + dialer := func(context.Context, string) (net.Conn, error) { return lis.Dial() } ctx := context.Background() - + conn, err := grpc.DialContext(ctx, "", grpc.WithContextDialer(dialer), grpc.WithInsecure()) t.Cleanup(func() { conn.Close() @@ -55,18 +51,16 @@ func TestGrpcVersion(t *testing.T) { } client := version.NewVersionServiceClient(conn) - res , err := client.Version(context.Background(), &version.VersionRequest{}) + res, err := client.Version(context.Background(), &version.VersionRequest{}) - // Check the status code is what we expect. if _, ok := status.FromError(err); !ok { t.Errorf("Version returned type %T, want %T", err, status.Error) } - // Check the response body is what we expect. expected := fmt.Sprintf(".*%s.*", v.VERSION) r := regexp.MustCompile(expected) if !r.MatchString(res.String()) { t.Fatalf("Returned unexpected body:\ngot \n%v \nwant \n%s", res, expected) } -} \ No newline at end of file +}