From 9a6ba3e159307423f7557696c89dc042d3fcc8f9 Mon Sep 17 00:00:00 2001 From: Till Kuhn Date: Fri, 24 Jan 2025 13:47:59 +0100 Subject: [PATCH 1/4] add grpc support --- Makefile | 20 +++++++++++++++++++- go.mod | 5 +++++ go.sum | 10 ++++++++++ internal/pb/.gitignore | 2 ++ internal/pb/billy.proto | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 internal/pb/.gitignore create mode 100644 internal/pb/billy.proto diff --git a/Makefile b/Makefile index 3cf1b58..dda82e8 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,7 @@ lint: ## Lint go code @golangci-lint run --fix .PHONY: test -test: lint ## Run tests with coverage, implies lint +test: lint ## Run tests with coverage, implies lint, excludes generated *.pb.go files @if hash gotest 2>/dev/null; then \ gotest -v -coverpkg=./... -coverprofile=coverage.out ./...; \ else go test -v -coverpkg=./... -coverprofile=coverage.out ./...; fi @@ -136,6 +136,24 @@ install: clean build ## Install as launchd managed service @tail $(HOME)/.billy-idle/default/agent.log +# https://grpc.io/docs/languages/go/basics/ + https://grpc.io/docs/languages/go/quickstart/ +# for compiler plugins, make sure "$PATH:$(go env GOPATH)/bin" is on your path +.PHONY: grpc-install +grpc-install: ## Installs protobuf and Go gRPC compiler plugins + brew list --versions protobuf || brew install protobuf + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + @protoc-gen-go --version || echo 'Make sure GOPATH/bin is on your PATH' + @protoc-gen-go-grpc --version + go get -u google.golang.org/grpc + + +.PHONY: grpc-gen +grpc-gen: ## Generate gRPC Code with protoc + protoc --go_out=. --go_opt=paths=source_relative \ + --go-grpc_out=. --go-grpc_opt=paths=source_relative \ + internal/pb/billy.proto + .PHONY: logs logs: ## Show agent logs @tail -120 $(HOME)/.billy-idle/default/agent.log diff --git a/go.mod b/go.mod index 2b36580..fa569ae 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,12 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6 // indirect modernc.org/libc v1.61.9 // indirect diff --git a/go.sum b/go.sum index 8de3375..97250f4 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -75,9 +77,17 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/pb/.gitignore b/internal/pb/.gitignore new file mode 100644 index 0000000..de802d0 --- /dev/null +++ b/internal/pb/.gitignore @@ -0,0 +1,2 @@ +# generated protoc files should *not* be under version control +*.pb.go diff --git a/internal/pb/billy.proto b/internal/pb/billy.proto new file mode 100644 index 0000000..3879d93 --- /dev/null +++ b/internal/pb/billy.proto @@ -0,0 +1,32 @@ +// Tutorials +// https://grpc.io/docs/languages/go/quickstart/ +// https://grpc.io/docs/languages/go/basics/ +// https://pascalallen.medium.com/how-to-build-a-grpc-server-in-go-943f337c4e05 +// +// Compile with +// protoc --go_out=. --go_opt=paths=source_relative \ +// --go-grpc_out=. --go-grpc_opt=paths=source_relative \ +// billy.proto + +syntax = "proto3"; + +option go_package = "github.com/tillkuhn/billy-idle/pb"; + +package pb; + + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 5a1475320c7ac6ddf8dc1681a1bd7bfdeac8210b Mon Sep 17 00:00:00 2001 From: Till Kuhn Date: Fri, 24 Jan 2025 14:29:47 +0100 Subject: [PATCH 2/4] play around with empty --- Makefile | 4 ++-- internal/pb/billy.proto | 44 ++++++++++++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index dda82e8..bc2d902 100644 --- a/Makefile +++ b/Makefile @@ -71,8 +71,8 @@ lint: ## Lint go code .PHONY: test test: lint ## Run tests with coverage, implies lint, excludes generated *.pb.go files @if hash gotest 2>/dev/null; then \ - gotest -v -coverpkg=./... -coverprofile=coverage.out ./...; \ - else go test -v -coverpkg=./... -coverprofile=coverage.out ./...; fi + gotest -v -coverpkg=./... -coverprofile=coverage.out $(shell go list ./... | grep -v internal/pb); \ + else go test -v -coverpkg=./... -coverprofile=coverage.out $(shell go list ./... | grep -v internal/pb); fi @go tool cover -func coverage.out | grep "total:" go tool cover -html=coverage.out -o coverage.html @echo For coverage report open coverage.html diff --git a/internal/pb/billy.proto b/internal/pb/billy.proto index 3879d93..b3f5e98 100644 --- a/internal/pb/billy.proto +++ b/internal/pb/billy.proto @@ -1,4 +1,5 @@ -// Tutorials +// Go gRPC Tutorials: +// // https://grpc.io/docs/languages/go/quickstart/ // https://grpc.io/docs/languages/go/basics/ // https://pascalallen.medium.com/how-to-build-a-grpc-server-in-go-943f337c4e05 @@ -12,21 +13,42 @@ syntax = "proto3"; option go_package = "github.com/tillkuhn/billy-idle/pb"; -package pb; +import "google/protobuf/empty.proto"; +package pb; // The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} +service Billy { + // set mode (see commends below for empty) + rpc SetMode (ModeRequest) returns (ModeResponse) {} + rpc Status (google.protobuf.Empty) returns (ModeResponse) {} +} + +// The request message containing the mode to set +message ModeRequest { + Mode mode = 1; } -// The request message containing the user's name. -message HelloRequest { - string name = 1; +// The response message containing the newly applied mode +message ModeResponse { + Mode mode = 1; } -// The response message containing the greetings -message HelloReply { - string message = 1; +// https://www.reddit.com/r/golang/comments/dysrzw/protobuf_and_enum_type/ +// https://protobuf.dev/programming-guides/enum/ +enum Mode { + AUTO = 0; + IDLE = 1; + BUSY = 2; } + +// https://stackoverflow.com/questions/31768665/can-i-define-a-grpc-call-with-a-null-request-or-response +// Looking through the default proto files, I came across Empty that is exactly like the Null type I suggested above :) +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// From f922104434213e9dbbc592100912d1b92187fca5 Mon Sep 17 00:00:00 2001 From: Till Kuhn Date: Fri, 24 Jan 2025 14:59:38 +0100 Subject: [PATCH 3/4] Add gRCP client server code --- cmd/track.go | 6 ++++ cmd/wsp.go | 57 +++++++++++++++++++++++++++++++ go.mod | 9 ++--- go.sum | 74 ++++++++++++++++------------------------- internal/pb/billy.proto | 9 +++-- pkg/tracker/tracker.go | 30 ++++++++++++++++- 6 files changed, 130 insertions(+), 55 deletions(-) create mode 100644 cmd/wsp.go diff --git a/cmd/track.go b/cmd/track.go index 7b94bbc..17036f8 100644 --- a/cmd/track.go +++ b/cmd/track.go @@ -53,6 +53,12 @@ func track(ctx context.Context) { go func() { t.Track(ctx) }() + // Experimental gRPC server + go func() { + if err := t.ServeGRCP(); err != nil { + log.Fatal(err) + } + }() sig := <-sigChan log.Printf("🔫 Received signal %v, initiate shutdown", sig) diff --git a/cmd/wsp.go b/cmd/wsp.go new file mode 100644 index 0000000..164f718 --- /dev/null +++ b/cmd/wsp.go @@ -0,0 +1,57 @@ +package cmd + +import ( + "context" + "github.com/golang/protobuf/ptypes/empty" + "github.com/tillkuhn/billy-idle/internal/pb" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "log" + "time" + + "github.com/spf13/cobra" +) + +// wspCmd represents the wsp command +var wspCmd = &cobra.Command{ + Use: "wsp", + Short: "What's up?", + Long: `Returns status info from the current tracker instance`, + Run: func(cmd *cobra.Command, args []string) { + status(cmd.Context()) + + }, +} + +func status(ctx context.Context) { + addr := "localhost:50051" + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer func(conn *grpc.ClientConn) { _ = conn.Close() }(conn) + c := pb.NewBillyClient(conn) + + // Contact the server and print out its response. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := c.Status(ctx, &empty.Empty{}) + if err != nil { + log.Fatalf("could not get status: %v", err) + } + log.Printf("Greeting: %s", r.GetMessage()) +} + +func init() { + rootCmd.AddCommand(wspCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // wspCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // wspCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/go.mod b/go.mod index fa569ae..bff8a87 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,14 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/brianvoe/gofakeit/v7 v7.1.2 github.com/fatih/color v1.18.0 + github.com/golang/protobuf v1.5.4 github.com/jmoiron/sqlx v1.4.0 github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 + google.golang.org/grpc v1.70.0 + google.golang.org/protobuf v1.36.3 modernc.org/sqlite v1.34.5 ) @@ -18,7 +21,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -33,13 +35,8 @@ require ( golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect - google.golang.org/grpc v1.70.0 // indirect - google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6 // indirect modernc.org/libc v1.61.9 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.8.2 // indirect - modernc.org/strutil v1.2.1 // indirect - modernc.org/token v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index 97250f4..d7923e8 100644 --- a/go.sum +++ b/go.sum @@ -11,14 +11,20 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +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-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= @@ -26,14 +32,10 @@ github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -57,31 +59,33 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao= google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= @@ -92,48 +96,26 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= -modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= -modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= -modernc.org/ccgo/v4 v4.23.12 h1:UF08a38c4B+K3VoGipBrVWLFUCHd8+X20QZtFAIlQNk= +modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00= +modernc.org/ccgo/v4 v4.23.13/go.mod h1:vdN4h2WR5aEoNondUx26K7G8X+nuBscYnAEWSRmN2/0= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v2 v2.6.1 h1:+Qf6xdG8l7B27TQ8D8lw/iFMUj1RXRBOuMUWziJOsk8= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= -modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6 h1:JoKwHjIFumiKrjMbp1cNbC5E9UyCgA/ZcID0xOWQ2N8= -modernc.org/gc/v3 v3.0.0-20250105121824-520be1a3aee6/go.mod h1:LG5UO1Ran4OO0JRKz2oNiXhR5nNrgz0PzH7UKhz0aMU= -modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE= -modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0= -modernc.org/libc v1.61.8 h1:50KrjlFFoKq9ABh+bNVUf5SfVfQ4NY7CEyFBh65qc60= -modernc.org/libc v1.61.8/go.mod h1:XloulGc0yIRM+91kbwrp7jNi/mfYPAvDOD2qwzWEij0= +modernc.org/gc/v2 v2.6.1/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/libc v1.61.9 h1:PLSBXVkifXGELtJ5BOnBUyAHr7lsatNwFU/RRo4kfJM= modernc.org/libc v1.61.9/go.mod h1:61xrnzk/aR8gr5bR7Uj/lLFLuXu2/zMpIjcry63Eumk= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/pb/billy.proto b/internal/pb/billy.proto index b3f5e98..927fd64 100644 --- a/internal/pb/billy.proto +++ b/internal/pb/billy.proto @@ -1,6 +1,6 @@ // Go gRPC Tutorials: // -// https://grpc.io/docs/languages/go/quickstart/ +// https://grpc.io/docs/languages/go/quickstart/ and https://github.com/grpc/grpc-go/tree/master/examples/helloworld // https://grpc.io/docs/languages/go/basics/ // https://pascalallen.medium.com/how-to-build-a-grpc-server-in-go-943f337c4e05 // @@ -21,7 +21,7 @@ package pb; service Billy { // set mode (see commends below for empty) rpc SetMode (ModeRequest) returns (ModeResponse) {} - rpc Status (google.protobuf.Empty) returns (ModeResponse) {} + rpc Status (google.protobuf.Empty) returns (StatusResponse) {} } // The request message containing the mode to set @@ -34,6 +34,11 @@ message ModeResponse { Mode mode = 1; } +// The response message containing the newly applied mode +message StatusResponse { + string message = 1; +} + // https://www.reddit.com/r/golang/comments/dysrzw/protobuf_and_enum_type/ // https://protobuf.dev/programming-guides/enum/ enum Mode { diff --git a/pkg/tracker/tracker.go b/pkg/tracker/tracker.go index 2ea6adc..7865d96 100644 --- a/pkg/tracker/tracker.go +++ b/pkg/tracker/tracker.go @@ -3,8 +3,12 @@ package tracker import ( "context" "fmt" + "github.com/golang/protobuf/ptypes/empty" + "github.com/tillkuhn/billy-idle/internal/pb" + "google.golang.org/grpc" "log" "math/rand/v2" + "net" "os" "sync" "time" @@ -13,11 +17,13 @@ import ( "github.com/jmoiron/sqlx" ) -// Tracker tracks idle state periodically and persists state changes in DB +// Tracker tracks idle state periodically and persists state changes in DB, +// also used to implement gRPC BillyServer type Tracker struct { opts *Options db *sqlx.DB wg sync.WaitGroup + pb.UnimplementedBillyServer } // New returns a new Tracker configured with the given Options @@ -40,6 +46,28 @@ func NewWithDB(opts *Options, db *sqlx.DB) *Tracker { } } +// ServeGRCP experimental Server for gRCP support +func (t *Tracker) ServeGRCP() error { + grpcPort := 50051 + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort)) + if err != nil { + return err + } + s := grpc.NewServer() + pb.RegisterBillyServer(s, t) + log.Printf("gRCP server listening at %v", lis.Addr()) + if err := s.Serve(lis); err != nil { + return err + } + return nil +} + +// Status implements pb.BillyServer +func (t *Tracker) Status(_ context.Context, _ *empty.Empty) (*pb.StatusResponse, error) { + log.Println("Received: status request") + return &pb.StatusResponse{Message: "Hello I am up and running"}, nil +} + // Track starts the idle/Busy tracker in a loop that runs until the context is cancelled func (t *Tracker) Track(ctx context.Context) { t.wg.Add(1) From 4de4242ef5cc858b80289d3ca765171263d04e71 Mon Sep 17 00:00:00 2001 From: Till Kuhn Date: Tue, 28 Jan 2025 20:59:30 +0100 Subject: [PATCH 4/4] Add rudimentary gRPC support for local client/server communication --- .gitignore | 2 +- .golangci.yml | 2 ++ Makefile | 21 +++++++++++++------- cmd/wsp.go | 32 +++++++++++++++++++----------- cmd/wsp_test.go | 38 +++++++++++++++++++++++++++++++++++ internal/pb/billy.proto | 2 ++ pkg/tracker/tracker.go | 44 +++++++++++++++++++++++++---------------- pkg/tracker/types.go | 1 + 8 files changed, 105 insertions(+), 37 deletions(-) create mode 100644 cmd/wsp_test.go diff --git a/.gitignore b/.gitignore index edeadeb..81da523 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,4 @@ fabric.properties .env dist/ -coverage.html +coverage.* diff --git a/.golangci.yml b/.golangci.yml index 24ee1bb..6ae334b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -70,6 +70,8 @@ issues: linters: [ gochecknoglobals,mnd ] - path: cmd/track.go linters: [ gochecknoglobals,mnd ] + - path: cmd/wsp.go + linters: [ gochecknoglobals,mnd ] - path: pkg/tracker/report.go linters: [ mnd ] # magic number detection is annoying here - path: pkg/tracker/tracker.go diff --git a/Makefile b/Makefile index bc2d902..f82da6d 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ endif APP_NAME=billy-idle BINARY ?= billy +DEFAULT_ENV ?= default LAUNCHD_LABEL ?= com.github.tillkuhn.$(APP_NAME) #------------------- @@ -68,11 +69,13 @@ lint: ## Lint go code @go fmt ./... @golangci-lint run --fix +# $(shell go list ./... | grep -v internal/pb) .PHONY: test test: lint ## Run tests with coverage, implies lint, excludes generated *.pb.go files @if hash gotest 2>/dev/null; then \ - gotest -v -coverpkg=./... -coverprofile=coverage.out $(shell go list ./... | grep -v internal/pb); \ - else go test -v -coverpkg=./... -coverprofile=coverage.out $(shell go list ./... | grep -v internal/pb); fi + gotest -v -coverpkg=./... -coverprofile=coverage.out.tmp ./... ; \ + else go test -v -coverpkg=./... -coverprofile=coverage.out.tmp ./... ; fi + grep -v ".pb.go" coverage.out.tmp > coverage.out @go tool cover -func coverage.out | grep "total:" go tool cover -html=coverage.out -o coverage.html @echo For coverage report open coverage.html @@ -103,15 +106,19 @@ run: ## Run app in tracker mode (dev env), add -drop-create to recreate db .PHONY: punch punch: ## Show punch clock report for default db - go run main.go --debug punch --env default + go run main.go --debug punch --env $(DEFAULT_ENV) -.PHONY: report-dev -report-dev: ## Show report for dev env db - go run main.go --debug report --env dev +.PHONY: wsp +wsp: ## Show status using gRPC Client + go run main.go --debug wsp .PHONY: report report: ## Show report for default db - go run main.go --debug report --env default + go run main.go --debug report --env $(DEFAULT_ENV) + +.PHONY: report-dev +report-dev: ## Show report for dev env db + go run main.go --debug report --env dev .PHONY: run-help run-help: ## Run app in help mode diff --git a/cmd/wsp.go b/cmd/wsp.go index 164f718..967a7b9 100644 --- a/cmd/wsp.go +++ b/cmd/wsp.go @@ -2,48 +2,56 @@ package cmd import ( "context" + "strconv" + "time" + "github.com/golang/protobuf/ptypes/empty" "github.com/tillkuhn/billy-idle/internal/pb" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "log" - "time" "github.com/spf13/cobra" ) +var ( + gRPCPort int +) + // wspCmd represents the wsp command var wspCmd = &cobra.Command{ Use: "wsp", Short: "What's up?", Long: `Returns status info from the current tracker instance`, - Run: func(cmd *cobra.Command, args []string) { - status(cmd.Context()) - + RunE: func(cmd *cobra.Command, _ []string) error { + return status(cmd.Context()) }, } -func status(ctx context.Context) { - addr := "localhost:50051" +func status(ctx context.Context) error { + addr := "localhost:" + strconv.Itoa(gRPCPort) conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Fatalf("did not connect: %v", err) + return err } defer func(conn *grpc.ClientConn) { _ = conn.Close() }(conn) c := pb.NewBillyClient(conn) // Contact the server and print out its response. - ctx, cancel := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() - r, err := c.Status(ctx, &empty.Empty{}) + // https://github.com/grpc/grpc-go/blob/master/examples/features/wait_for_ready/main.go#L93 + r, err := c.Status(ctx, &empty.Empty{}, grpc.WaitForReady(true)) if err != nil { - log.Fatalf("could not get status: %v", err) + return err } - log.Printf("Greeting: %s", r.GetMessage()) + _, _ = rootCmd.OutOrStdout().Write([]byte("Response: " + r.GetMessage() + "\n")) + // log.Printf("Greeting: %s", r.GetMessage()) + return nil } func init() { rootCmd.AddCommand(wspCmd) + wspCmd.PersistentFlags().IntVar(&gRPCPort, "port", 50051, "Port for gRPC Communication") // Here you will define your flags and configuration settings. diff --git a/cmd/wsp_test.go b/cmd/wsp_test.go new file mode 100644 index 0000000..847c6d1 --- /dev/null +++ b/cmd/wsp_test.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "bytes" + "slices" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tillkuhn/billy-idle/pkg/tracker" +) + +var grpcPort = 50052 // use different port for test to avoid conflicts +func TestWSPStatusError(t *testing.T) { + actual := new(bytes.Buffer) + rootCmd.SetOut(actual) + rootCmd.SetErr(actual) + wspArgs := []string{"--port", strconv.Itoa(grpcPort)} + rootCmd.SetArgs(slices.Insert(wspArgs, 0, wspCmd.Use)) + // Returns "Error: rpc error: code = DeadlineExceeded desc = context deadline exceeded\nUsage:\n b + // if no server + opts := &tracker.Options{ + GRPCPort: grpcPort, + ClientID: "test", + AppRoot: defaultAppRoot(), + } + tr := tracker.New(opts) + go func() { + if err := tr.ServeGRCP(); err != nil { + t.Log(err) + t.Fail() + } + }() + // assert.NoError(t, tr.ServeGRCP()) + err := rootCmd.Execute() + assert.NoError(t, err) + assert.Contains(t, actual.String(), "I am up and running") +} diff --git a/internal/pb/billy.proto b/internal/pb/billy.proto index 927fd64..d5393dd 100644 --- a/internal/pb/billy.proto +++ b/internal/pb/billy.proto @@ -14,6 +14,7 @@ syntax = "proto3"; option go_package = "github.com/tillkuhn/billy-idle/pb"; import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; package pb; @@ -37,6 +38,7 @@ message ModeResponse { // The response message containing the newly applied mode message StatusResponse { string message = 1; + google.protobuf.Timestamp time = 7; } // https://www.reddit.com/r/golang/comments/dysrzw/protobuf_and_enum_type/ diff --git a/pkg/tracker/tracker.go b/pkg/tracker/tracker.go index 7865d96..c6157fb 100644 --- a/pkg/tracker/tracker.go +++ b/pkg/tracker/tracker.go @@ -3,9 +3,6 @@ package tracker import ( "context" "fmt" - "github.com/golang/protobuf/ptypes/empty" - "github.com/tillkuhn/billy-idle/internal/pb" - "google.golang.org/grpc" "log" "math/rand/v2" "net" @@ -13,6 +10,11 @@ import ( "sync" "time" + "github.com/golang/protobuf/ptypes/empty" + "github.com/tillkuhn/billy-idle/internal/pb" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/brianvoe/gofakeit/v7" "github.com/jmoiron/sqlx" ) @@ -20,10 +22,11 @@ import ( // Tracker tracks idle state periodically and persists state changes in DB, // also used to implement gRPC BillyServer type Tracker struct { - opts *Options - db *sqlx.DB - wg sync.WaitGroup - pb.UnimplementedBillyServer + opts *Options + db *sqlx.DB + grpcServer *grpc.Server + wg sync.WaitGroup + pb.UnimplementedBillyServer // Tracker implements billy gRPC Server } // New returns a new Tracker configured with the given Options @@ -31,6 +34,9 @@ func New(opts *Options) *Tracker { if opts.Out == nil { opts.Out = os.Stdout } + if opts.GRPCPort == 0 { + opts.GRPCPort = 50051 + } db, err := initDB(opts) if err != nil { log.Fatal(err) @@ -41,31 +47,33 @@ func New(opts *Options) *Tracker { // NewWithDB returns a new Tracker configured with the given Options and DB, good for testing func NewWithDB(opts *Options, db *sqlx.DB) *Tracker { return &Tracker{ - opts: opts, - db: db, + opts: opts, + db: db, + grpcServer: grpc.NewServer(), } } // ServeGRCP experimental Server for gRCP support func (t *Tracker) ServeGRCP() error { - grpcPort := 50051 - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort)) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", t.opts.GRPCPort)) if err != nil { return err } - s := grpc.NewServer() - pb.RegisterBillyServer(s, t) - log.Printf("gRCP server listening at %v", lis.Addr()) - if err := s.Serve(lis); err != nil { + log.Printf("👂 Registering gRCP server to listen at %v", lis.Addr()) + pb.RegisterBillyServer(t.grpcServer, t) + if err := t.grpcServer.Serve(lis); err != nil { return err } return nil } -// Status implements pb.BillyServer +// Status as per pb.BillyServer func (t *Tracker) Status(_ context.Context, _ *empty.Empty) (*pb.StatusResponse, error) { log.Println("Received: status request") - return &pb.StatusResponse{Message: "Hello I am up and running"}, nil + return &pb.StatusResponse{ + Time: timestamppb.Now(), + Message: "Hi! I am up and running in env=" + t.opts.Env, + }, nil } // Track starts the idle/Busy tracker in a loop that runs until the context is cancelled @@ -88,6 +96,8 @@ func (t *Tracker) Track(ctx context.Context) { // we're finished here, make sure latest status is written to db, must use a fresh context msg := fmt.Sprintf("🛑 Tracker stopped after %v %s time", ist.TimeSinceLastSwitch(), ist.State()) _ = t.completeTrackRecord(context.Background(), ist.id, msg) + log.Printf("👂 Stopping gRCP server on port %d", t.opts.GRPCPort) + t.grpcServer.GracefulStop() done = true default: idleMillis, err := IdleTime(ctx, t.opts.Cmd) diff --git a/pkg/tracker/types.go b/pkg/tracker/types.go index 1a72545..47567cf 100644 --- a/pkg/tracker/types.go +++ b/pkg/tracker/types.go @@ -22,6 +22,7 @@ type Options struct { MaxBusy time.Duration RegBusy time.Duration Out io.Writer + GRPCPort int } func (o Options) AppDir() string {