Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Connect instead of gRPC #155

Merged
merged 3 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ 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
install-tools: ensure-local-bin-dir
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
14 changes: 5 additions & 9 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@ managed:
plugins:
- name: go
opt: paths=source_relative
out: ./perseusapi
- name: go-grpc
out: perseusapi
- name: connect-go
opt:
- paths=source_relative
out: ./perseusapi
- name: grpc-gateway
opt:
- paths=source_relative
- allow_repeated_fields_in_body=true
out: ./perseusapi
out: perseusapi
- name: openapiv2
dylan-bourque marked this conversation as resolved.
Show resolved Hide resolved
opt:
- allow_repeated_fields_in_body=true
out: ./docs
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
}
5 changes: 5 additions & 0 deletions docs/perseus.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"schema": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/perseusapiModule"
}
}
Expand Down Expand Up @@ -318,6 +319,7 @@
"modules": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/perseusapiModule"
}
},
Expand All @@ -332,6 +334,7 @@
"modules": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/perseusapiModule"
}
},
Expand Down Expand Up @@ -372,6 +375,7 @@
"modules": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/perseusapiModule"
}
},
Expand Down Expand Up @@ -405,6 +409,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
Expand Down
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
25 changes: 17 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@ module github.com/CrowdStrike/perseus
go 1.21

require (
connectrpc.com/connect v1.16.0
connectrpc.com/otelconnect v0.7.0
connectrpc.com/vanguard v0.1.0
github.com/Masterminds/squirrel v1.5.4
github.com/bufbuild/httplb v0.3.0
github.com/go-git/go-git/v5 v5.12.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
github.com/jackc/pgx/v4 v4.18.3
github.com/jmoiron/sqlx v1.4.0
github.com/mattn/go-isatty v0.0.20
github.com/prometheus/client_golang v1.19.0
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/theckman/yacspin v0.13.12
go.opentelemetry.io/otel/exporters/prometheus v0.47.0
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 All @@ -38,23 +40,30 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opentelemetry.io/otel v1.25.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
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 All @@ -69,9 +78,9 @@ require (
github.com/jackc/pgtype v1.14.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0
golang.org/x/sync v0.7.0
golang.org/x/sys v0.18.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
Loading