Skip to content

Commit

Permalink
feat: migrate client from gRPC to Connect
Browse files Browse the repository at this point in the history
replace gRPC client usage in the CLI with Connect client
remove gRPC code generation
updated README.md to reflect the switch to Connect
  • Loading branch information
Dylan Bourque committed May 3, 2024
1 parent 1a0e2a5 commit baaefc0
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 996 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,8 @@ test:
@go test ${TEST_OPTS} ${TEST_RACE} ./...

BUILD_TIME_TOOLS =\
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway\
github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \
google.golang.org/protobuf/cmd/protoc-gen-go \
google.golang.org/grpc/cmd/protoc-gen-go-grpc \
google.golang.org/protobuf/cmd/protoc-gen-go \
connectrpc.com/connect/cmd/protoc-gen-connect-go

.PHONY: install-tools
Expand Down
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,23 @@ version.

##### Service Architecture

The `perseus` service exposes a gRPC API, along with JSON/REST mappings using the [gRPC Gateway](https://github.com/grpc-ecosystem/grpc-gateway) project for exposure to web-based consumers via a set of paths under `/api/v1/*`. Both endpoints, plus a very basic web UI for testing, are served on a single port using [cmux](https://github.com/soheilhy/cmux).
The `perseus` service is built on [Connect](https://connectrpc.com) to provide an API that supports
binary gRPC and HTTP JSON/REST requests. Both RPC bindings are provided on a single port using
[Vanguard](https://connectrpc.com/vanguard), with the JSON/REST endpoints at a nested path of `/api/v1/*`.
Additionally, a basic web-based user interface is available at `/ui`.

In addition to the interactive endpoints, the service also exports an HTTP health check at `/healthz/` and basic Prometheus metrics at `/metrics/`. For debugging and troubleshooting, the service supports retrieving [Go `pprof` data](https://pkg.go.dev/net/http/pprof) via HTTP at `/debug/pprof*`.
In addition to the interactive endpoints, the service also exports an HTTP health check at `/healthz`
and basic Prometheus metrics at `/metrics`. For debugging and troubleshooting, the service supports
retrieving [Go `pprof` data](https://pkg.go.dev/net/http/pprof) via HTTP at `/debug/pprof*`.

#### Running the Service

For simplicity, we publish a pre-built Docker image (based on a `scratch` base) to the GitHub Container Registry when each release is tagged. The image runs `perseus server` and exposes port 31138, which you can map to whatever is appropriate for your environment. You will need to provide the URL location of the Perseus database, along with a valid username and password to connect, via environment variables. The default database name is "perseus", but you can override that by also providing a `DB_NAME` environment variable.
For simplicity, we publish a pre-built Docker image (based on a `scratch` base) to the GitHub Container
Registry when each release is tagged. The image runs `perseus server` and exposes port 31138, which
you can map to whatever is appropriate for your environment. You will need to provide the URL location
of the Perseus database, along with a valid username and password to connect, via environment variables.
The default database name is "perseus", but you can override that by also providing a `DB_NAME`
environment variable.

> docker run --rm -e DB_ADDR=... -e DB_USER=... -e DBPASS=... -p 31138:31138 ghcr.io/crowdstrike/perseus:latest

Expand Down Expand Up @@ -108,7 +118,8 @@ The first two commands return modules and versions based on glob pattern matches
module github.com/example/foo has version v1.1.0
...

The `ancestors` and `descendants` commands walk the graph to return dependency trees, either what a specified version of a module depends on or what modules depend on it.
The `ancestors` and `descendants` commands walk the graph to return dependency trees, either what a
specified version of a module depends on or what modules depend on it.

# show the modules that v1.2.0 of github.com/example/foo depends on as a tabular list
> perseus query ancestors github.com/example/foo@v1.2.0 --list
Expand All @@ -135,7 +146,8 @@ The `ancestors` and `descendants` commands walk the graph to return dependency t
]
}

In addition to text-based results using `--json`, `--list` or `--format`, the `ancestor` and `descendant` commands also supports outputting DOT directed graphs using the `--dot` flag.
In addition to text-based results using `--json`, `--list` or `--format`, the `ancestor` and `descendant`
commands also supports outputting DOT directed graphs using the `--dot` flag.

# generate an SVG image of the dependency graph for the highest version of github.com/example/foo
> perseus query ancestors github.com/example/foo --dot | dot -Tsvg -o ~/foo_deps.svg
Expand Down
17 changes: 4 additions & 13 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,13 @@ managed:
plugins:
- name: go
opt: paths=source_relative
out: ./perseusapi
- name: go-grpc
opt:
- paths=source_relative
out: ./perseusapi
- name: grpc-gateway
out: perseusapi
- name: connect-go
opt:
- paths=source_relative
- allow_repeated_fields_in_body=true
out: ./perseusapi
out: perseusapi
- name: openapiv2
opt:
- allow_repeated_fields_in_body=true
out: ./docs
- name: connect-go
opt:
- paths=source_relative
out: ./perseusapi
out: docs

59 changes: 17 additions & 42 deletions client_config.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package main

import (
"context"
"fmt"
"crypto/tls"
"os"
"strconv"
"time"

"connectrpc.com/connect"
"github.com/bufbuild/httplb"
"github.com/spf13/pflag"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"

"github.com/CrowdStrike/perseus/perseusapi"
"github.com/CrowdStrike/perseus/perseusapi/perseusapiconnect"
)

// package variables to hold CLI flag values
Expand Down Expand Up @@ -84,40 +79,20 @@ func readClientConfigFlags(fset *pflag.FlagSet) []clientOption {
return opts
}

func (conf *clientConfig) dialServer() (client perseusapi.PerseusServiceClient, err error) {
// translate RPC errors to human-friendly ones on return
defer func() {
switch err {
case context.DeadlineExceeded:
err = fmt.Errorf("timed out trying to connect to the Perseus server")
default:
if err != nil {
switch status.Code(err) {
case codes.Unavailable:
err = fmt.Errorf("unable to connect to the Perseus server")
default:
}
}
func (conf *clientConfig) getClient() (client perseusapiconnect.PerseusServiceClient) {
opts := []httplb.ClientOption{}
if !conf.disableTLS {
tlsc := tls.Config{
MinVersion: tls.VersionTLS13,
}
}()

// setup gRPC connection options and connect
dialOpts := []grpc.DialOption{
grpc.WithBlock(),
}
if conf.disableTLS {
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
} else {
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(nil)))
}
logger.Debug("connecting to Perseus server", "addr", conf.serverAddr, "useTLS", !conf.disableTLS)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := grpc.DialContext(ctx, conf.serverAddr, dialOpts...) //nolint: staticcheck // we specifically want WithBlock(), which is ignored by grpc.NewClient()
if err != nil {
return nil, err
opts = append(opts, httplb.WithTLSConfig(&tlsc, 0))
}

// create and return the client
return perseusapi.NewPerseusServiceClient(conn), nil
// we include WithGRPC() so that the CLI can hit an existing gRPC-based server instance
// - this may be removed at some point in the future
cc := perseusapiconnect.NewPerseusServiceClient(
httplb.NewClient(opts...),
conf.serverAddr,
connect.WithGRPC())
return cc
}
16 changes: 6 additions & 10 deletions findpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import (
"os"
"strings"

"connectrpc.com/connect"
"github.com/spf13/cobra"
"golang.org/x/mod/module"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/CrowdStrike/perseus/perseusapi"
"github.com/CrowdStrike/perseus/perseusapi/perseusapiconnect"
)

const findPathsExampleUsage = `# find any path between the latest version of github.com/example/foo and any version of gRPC
Expand Down Expand Up @@ -73,10 +72,7 @@ func runFindPathsCommand(cmd *cobra.Command, args []string) (err error) {
defer cancel()

updateSpinner("connecting to the server at " + conf.serverAddr)
ps, err := conf.dialServer()
if err != nil {
return err
}
ps := conf.getClient()

// validate the 'from' and 'to' modules, defaulting to the highest known release for 'from'
// if no version is specified
Expand Down Expand Up @@ -109,8 +105,8 @@ func runFindPathsCommand(cmd *cobra.Command, args []string) (err error) {
}()
for p := range pf.findPathsBetween(ctx, from, to) {
if p.err != nil {
if status.Code(p.err) == codes.Canceled || errors.Is(p.err, context.Canceled) {
// context cancellation is not a failure
// context cancellation is not a failure
if errors.Is(p.err, context.Canceled) || connect.CodeOf(err) == connect.CodeCanceled {
return nil
}
return p.err
Expand Down Expand Up @@ -154,7 +150,7 @@ func printJSONLinesTo(w io.Writer, paths [][]module.Version) {

// parseModuleArg parses the provided string as a Go module path, optionally with a version, and returns
// the parsed result. If no version is specified, the highest known version is used.
func parseModuleArg(ctx context.Context, arg string, client perseusapi.PerseusServiceClient, findLatest bool, status func(string)) (module.Version, error) {
func parseModuleArg(ctx context.Context, arg string, client perseusapiconnect.PerseusServiceClient, findLatest bool, status func(string)) (module.Version, error) {
defer status("")
var m module.Version
toks := strings.Split(arg, "@")
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
connectrpc.com/otelconnect v0.7.0
connectrpc.com/vanguard v0.1.0
github.com/Masterminds/squirrel v1.5.4
github.com/bufbuild/httplb v0.2.0
github.com/go-git/go-git/v5 v5.12.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
github.com/jackc/pgx/v4 v4.18.3
Expand All @@ -21,8 +22,6 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.25.0
golang.org/x/mod v0.17.0
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de
google.golang.org/grpc v1.63.2
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.33.0
)

Expand Down Expand Up @@ -64,6 +63,7 @@ require (
golang.org/x/tools v0.13.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bufbuild/httplb v0.2.0 h1:DlpvtxfbYrG05jmIn3voxpMmVsRzOE/DodxChNydd6w=
github.com/bufbuild/httplb v0.2.0/go.mod h1:zeSE2XFTI4UsLOWoDppU2ZRRTJRPQ3Izs86Js9GbuNM=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down Expand Up @@ -129,6 +131,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
Expand Down Expand Up @@ -354,10 +358,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
5 changes: 3 additions & 2 deletions pathfinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
"golang.org/x/mod/module"

"github.com/CrowdStrike/perseus/perseusapi"
"github.com/CrowdStrike/perseus/perseusapi/perseusapiconnect"
)

// newPathFinder initializes and returns a new [pathFinder] instance using the provided Perseus
// client, maximum depth, and status callback.
func newPathFinder(c perseusapi.PerseusServiceClient, maxDepth int, status func(string)) pathFinder {
func newPathFinder(c perseusapiconnect.PerseusServiceClient, maxDepth int, status func(string)) pathFinder {
return pathFinder{
c: c,
maxDepth: maxDepth,
Expand All @@ -23,7 +24,7 @@ func newPathFinder(c perseusapi.PerseusServiceClient, maxDepth int, status func(
// pathFinder queries the Perseus database to contruct dependency paths of up to maxDepth steps between
// two modules.
type pathFinder struct {
c perseusapi.PerseusServiceClient
c perseusapiconnect.PerseusServiceClient
status func(string)
maxDepth int

Expand Down
Loading

0 comments on commit baaefc0

Please sign in to comment.