From a69cd3f44c08088c4aabae15d94e569b250765a6 Mon Sep 17 00:00:00 2001 From: Ryan McGuire Date: Sun, 30 Mar 2025 20:19:25 -0400 Subject: [PATCH] implement reference grpc server --- Makefile | 3 +- .../demo/app/v1alpha1/app.pb.go | 0 .../demo/app/v1alpha1/app.pb.gw.go | 0 .../demo/app/v1alpha1/app.swagger.json | 0 .../demo/app/v1alpha1/app_grpc.pb.go | 0 config.yaml | 24 ++++-- contrib/schema.json | 25 ++++-- go.mod | 10 ++- go.sum | 4 + main.go | 7 ++ pkg/config/custom.go | 15 +++- pkg/demogrpc/demo.go | 86 +++++++++++++++++++ pkg/demogrpc/fact.go | 25 ++++++ pkg/demogrpc/server.go | 30 +++++++ 14 files changed, 202 insertions(+), 27 deletions(-) rename api/{v1alpha1 => }/demo/app/v1alpha1/app.pb.go (100%) rename api/{v1alpha1 => }/demo/app/v1alpha1/app.pb.gw.go (100%) rename api/{v1alpha1 => }/demo/app/v1alpha1/app.swagger.json (100%) rename api/{v1alpha1 => }/demo/app/v1alpha1/app_grpc.pb.go (100%) create mode 100644 pkg/demogrpc/demo.go create mode 100644 pkg/demogrpc/fact.go create mode 100644 pkg/demogrpc/server.go diff --git a/Makefile b/Makefile index 4576a43..d7d7756 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ CMD_NAME := demo-app # TODO: Update app name .PHONY: all test build docker install clean proto check_protoc VERSION ?= development # Default to "development" if VERSION is not set -APIVERSION := v1alpha1 -API_DIR := api/$(APIVERSION) +API_DIR := api/ PROTO_DIRS := $(wildcard proto/demo/app/*) # TODO: Update path (probably not demo) PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 OUTPUT_DIR := bin diff --git a/api/v1alpha1/demo/app/v1alpha1/app.pb.go b/api/demo/app/v1alpha1/app.pb.go similarity index 100% rename from api/v1alpha1/demo/app/v1alpha1/app.pb.go rename to api/demo/app/v1alpha1/app.pb.go diff --git a/api/v1alpha1/demo/app/v1alpha1/app.pb.gw.go b/api/demo/app/v1alpha1/app.pb.gw.go similarity index 100% rename from api/v1alpha1/demo/app/v1alpha1/app.pb.gw.go rename to api/demo/app/v1alpha1/app.pb.gw.go diff --git a/api/v1alpha1/demo/app/v1alpha1/app.swagger.json b/api/demo/app/v1alpha1/app.swagger.json similarity index 100% rename from api/v1alpha1/demo/app/v1alpha1/app.swagger.json rename to api/demo/app/v1alpha1/app.swagger.json diff --git a/api/v1alpha1/demo/app/v1alpha1/app_grpc.pb.go b/api/demo/app/v1alpha1/app_grpc.pb.go similarity index 100% rename from api/v1alpha1/demo/app/v1alpha1/app_grpc.pb.go rename to api/demo/app/v1alpha1/app_grpc.pb.go diff --git a/config.yaml b/config.yaml index 1b831c6..4337721 100644 --- a/config.yaml +++ b/config.yaml @@ -1,21 +1,29 @@ # yaml-language-server: $schema=contrib/schema.json # Custom demo-app config -demoType: - optName: demo opt - optField: 99 -demoField: 99 +timezone: EST5EDT +opts: + factLang: en + factType: random # go-app config -name: Demo go-app HTTP App +name: Demo go-app logging: format: console level: trace enabled: true otel: - enabled: false -grpc: - enabled: false + enabled: true + stdoutEnabled: true http: enabled: true listen: :8080 + logRequests: true +grpc: + enabled: true + enableReflection: true + listen: :8081 + grpcGatewayPath: /api + enableGRPCGateway: true + enableInstrumentation: true + logRequests: true diff --git a/contrib/schema.json b/contrib/schema.json index 3776e50..2e0d80a 100644 --- a/contrib/schema.json +++ b/contrib/schema.json @@ -2,10 +2,16 @@ "definitions": { "ConfigDemoOpts": { "properties": { - "optField": { - "type": "integer" + "factLang": { + "default": "en", + "type": "string" }, - "optName": { + "factType": { + "default": "random", + "enum": [ + "today", + "random" + ], "type": "string" } }, @@ -104,12 +110,6 @@ } }, "properties": { - "demoField": { - "type": "integer" - }, - "demoType": { - "$ref": "#/definitions/ConfigDemoOpts" - }, "environment": { "type": "string" }, @@ -125,9 +125,16 @@ "name": { "type": "string" }, + "opts": { + "$ref": "#/definitions/ConfigDemoOpts" + }, "otel": { "$ref": "#/definitions/ConfigOTELConfig" }, + "timezone": { + "default": "UTC", + "type": "string" + }, "version": { "type": "string" } diff --git a/go.mod b/go.mod index 6b9b94b..16288dd 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,14 @@ go 1.24.1 require ( gitea.libretechconsulting.com/rmcguire/go-app v0.9.2 + github.com/go-resty/resty/v2 v2.16.5 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 github.com/rs/zerolog v1.34.0 golang.org/x/sys v0.31.0 + google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 + google.golang.org/grpc v1.71.0 + google.golang.org/protobuf v1.36.6 + k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e ) require ( @@ -35,7 +41,6 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -50,8 +55,5 @@ require ( go.opentelemetry.io/proto/otlp v1.5.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/grpc v1.71.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/go.sum b/go.sum index 0e462bc..5043c42 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -139,3 +141,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= +k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/main.go b/main.go index 77813ee..243dc06 100644 --- a/main.go +++ b/main.go @@ -20,9 +20,11 @@ import ( "golang.org/x/sys/unix" "gitea.libretechconsulting.com/rmcguire/go-app/pkg/app" + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/grpc/opts" optshttp "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http/opts" "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/config" + "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/demogrpc" "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/demohttp" ) @@ -47,10 +49,15 @@ func main() { // Prepare servers demoHTTP := demohttp.NewDemoHTTPServer(ctx, demoApp) + demoGRPC := demogrpc.NewDemoGRPCServer(ctx, demoApp) // Prepare app app := &app.App{ AppContext: ctx, + GRPC: &opts.AppGRPC{ + Services: demoGRPC.GetServices(), + GRPCDialOpts: demoGRPC.GetDialOpts(), + }, HTTP: &optshttp.AppHTTP{ Ctx: ctx, HealthChecks: demoHTTP.GetHealthCheckFuncs(), diff --git a/pkg/config/custom.go b/pkg/config/custom.go index a281284..d2997eb 100644 --- a/pkg/config/custom.go +++ b/pkg/config/custom.go @@ -8,8 +8,8 @@ import ( ) type DemoConfig struct { - DemoField int `yaml:"demoField" json:"demoField,omitempty"` - DemoType *DemoOpts `yaml:"demoType" json:"demoType,omitempty"` + Timezone string `yaml:"timezone" json:"timezone,omitempty" default:"UTC"` + Opts *DemoOpts `yaml:"opts" json:"opts,omitempty"` // Embeds go-app config, used by go-app to // merge custom config into go-app config, and to produce @@ -18,6 +18,13 @@ type DemoConfig struct { } type DemoOpts struct { - OptField int `yaml:"optField" json:"optField,omitempty"` - OptName string `yaml:"optName" json:"optName,omitempty"` + FactLang string `yaml:"factLang" json:"factLang,omitempty" default:"en"` + FactType FactType `yaml:"factType" json:"factType,omitempty" default:"random" enum:"today,random"` } + +type FactType string + +const ( + TypeToday FactType = "today" + TypeRandom FactType = "random" +) diff --git a/pkg/demogrpc/demo.go b/pkg/demogrpc/demo.go new file mode 100644 index 0000000..fc78dd0 --- /dev/null +++ b/pkg/demogrpc/demo.go @@ -0,0 +1,86 @@ +package demogrpc + +import ( + "context" + "fmt" + + "github.com/go-resty/resty/v2" + "github.com/rs/zerolog" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + + grpccodes "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" + + pb "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/api/demo/app/v1alpha1" + "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/pkg/config" +) + +const ( + defaultFactType = config.TypeRandom + factsBaseURI = "https://uselessfacts.jsph.pl/api/v2/facts" +) + +type DemoGRPCServer struct { + tracer trace.Tracer + ctx context.Context + cfg *config.DemoConfig + client *resty.Client + pb.UnimplementedDemoAppServiceServer +} + +func NewDemoGRPCServer(ctx context.Context, cfg *config.DemoConfig) *DemoGRPCServer { + if cfg.Opts == nil { + cfg.Opts = &config.DemoOpts{} + } + + if cfg.Opts.FactType == "" { + cfg.Opts.FactType = config.TypeRandom + } + + return &DemoGRPCServer{ + ctx: ctx, + cfg: cfg, + tracer: otel.GetTracer(ctx, "demoGRPCServer"), + client: resty.New().SetBaseURL(factsBaseURI), + } +} + +func (d *DemoGRPCServer) GetDemo(ctx context.Context, req *pb.GetDemoRequest) ( + *pb.GetDemoResponse, error, +) { + ctx, span := d.tracer.Start(ctx, "getDemo", trace.WithAttributes( + attribute.String("language", req.GetLanguage()), + )) + defer span.End() + + r := d.client.R().SetContext(ctx) + + if req.GetLanguage() != "" { + r = r.SetQueryParam("language", req.GetLanguage()) + } + + fact := new(RandomFact) + r.SetResult(fact) + + resp, err := r.Get(fmt.Sprintf("/%s", d.cfg.Opts.FactType)) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + + zerolog.Ctx(d.ctx).Err(err).Send() + + return nil, status.Errorf(grpccodes.Unknown, "%s: %s", + err.Error(), string(resp.Body())) + } + + span.SetAttributes(attribute.String("fact", fact.Text)) + span.SetStatus(codes.Ok, "") + + zerolog.Ctx(d.ctx).Debug().Str("fact", fact.Text).Msg("retrieved fact") + + return fact.FactToProto(), nil +} diff --git a/pkg/demogrpc/fact.go b/pkg/demogrpc/fact.go new file mode 100644 index 0000000..44991c9 --- /dev/null +++ b/pkg/demogrpc/fact.go @@ -0,0 +1,25 @@ +package demogrpc + +import ( + "google.golang.org/protobuf/types/known/timestamppb" + + pb "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/api/demo/app/v1alpha1" +) + +type RandomFact struct { + ID string `json:"id,omitempty"` + Text string `json:"text,omitempty"` + Source string `json:"source,omitempty"` + SourceURL string `json:"source_url,omitempty"` + Language string `json:"language,omitempty"` + Permalink string `json:"permalink,omitempty"` +} + +func (f *RandomFact) FactToProto() *pb.GetDemoResponse { + return &pb.GetDemoResponse{ + Timestamp: timestamppb.Now(), + Fact: f.Text, + Source: f.SourceURL, + Language: f.Language, + } +} diff --git a/pkg/demogrpc/server.go b/pkg/demogrpc/server.go new file mode 100644 index 0000000..cf0fe18 --- /dev/null +++ b/pkg/demogrpc/server.go @@ -0,0 +1,30 @@ +package demogrpc + +import ( + "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/grpc/opts" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + demoAppPb "gitea.libretechconsulting.com/rmcguire/go-http-server-with-otel/api/demo/app/v1alpha1" +) + +func (ds *DemoGRPCServer) GetDialOpts() []grpc.DialOption { + return []grpc.DialOption{ + // NOTE: Necessary for grpc-gateway to connect to grpc server + // Update if grpc service has credentials, tls, etc.. + grpc.WithTransportCredentials(insecure.NewCredentials()), + } +} + +func (ds *DemoGRPCServer) GetServices() []*opts.GRPCService { + return []*opts.GRPCService{ + { + Name: "Demo App", + Type: &demoAppPb.DemoAppService_ServiceDesc, + Service: ds, + GwRegistrationFuncs: []opts.GwRegistrationFunc{ + demoAppPb.RegisterDemoAppServiceHandler, + }, + }, + } +}