Skip to content

Commit

Permalink
Merge pull request #325 from jzelinskie/pkg/cmd
Browse files Browse the repository at this point in the history
Export CLI commands as a library
  • Loading branch information
jzelinskie committed Dec 8, 2021
2 parents efdb8ad + 77667de commit f94d8a4
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 178 deletions.
1 change: 1 addition & 0 deletions .github/labeler.yml
Expand Up @@ -10,6 +10,7 @@
- "internal/services/v1alpha1/**/*"
"area/cli":
- "cmd/**/*"
- "pkg/cmd/**/*"
"area/dashboard":
- "internal/dashboard/**/*"
"area/datastore":
Expand Down
4 changes: 2 additions & 2 deletions .goreleaser.yml
Expand Up @@ -13,7 +13,7 @@ builds:
mod_timestamp: "{{ .CommitTimestamp }}"
ldflags:
- "-s -w"
- "-X {{ .ModulePath }}/internal/version.Version={{ .Version }}"
- "-X {{ .ModulePath }}/pkg/cmd/version.Version={{ .Version }}"
nfpms:
- vendor: "authzed inc."
homepage: "https://spicedb.io"
Expand Down Expand Up @@ -49,7 +49,7 @@ brews:
system "#{bin}/spicedb version"
install: |
if !File.exists? "spicedb"
system "go build --ldflags \"-s -w -X github.com/authzed/spicedb/internal/version.Version=$(git describe --always --abbrev=7 --dirty)\" ./cmd/spicedb"
system "go build --ldflags \"-s -w -X github.com/authzed/spicedb/pkg/cmd/version.Version=$(git describe --always --abbrev=7 --dirty)\" ./cmd/spicedb"
end
bin.install "spicedb"
(bash_completion/"spicedb").write Utils.safe_popen_read("#{bin}/spicedb", "completion", "bash")
Expand Down
79 changes: 46 additions & 33 deletions cmd/spicedb/main.go
Expand Up @@ -2,57 +2,70 @@ package main

import (
"math/rand"
"net/http"
"net/http/pprof"
"time"

"github.com/cespare/xxhash"
"github.com/jzelinskie/cobrautil"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog"
"github.com/sercand/kuberesolver/v3"
"google.golang.org/grpc/balancer"

consistentbalancer "github.com/authzed/spicedb/pkg/balancer"
cmdutil "github.com/authzed/spicedb/pkg/cmd"
"github.com/authzed/spicedb/pkg/cmd/migrate"
"github.com/authzed/spicedb/pkg/cmd/root"
"github.com/authzed/spicedb/pkg/cmd/serve"
"github.com/authzed/spicedb/pkg/cmd/version"
)

const (
hashringReplicationFactor = 20
backendsPerKey = 1
)

var defaultPreRunE = cobrautil.CommandStack(
cobrautil.SyncViperPreRunE("spicedb"),
cobrautil.ZeroLogPreRunE("log", zerolog.InfoLevel),
cobrautil.OpenTelemetryPreRunE("otel", zerolog.InfoLevel),
)

func metricsHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return mux
}

func main() {
// Set up a seed for randomness
rand.Seed(time.Now().UnixNano())

// enable kubernetes grpc resolver
// Enable Kubernetes gRPC resolver
kuberesolver.RegisterInCluster()
// enable consistent hashring grpc load balancer
balancer.Register(consistentbalancer.NewConsistentHashringBuilder(xxhash.Sum64, hashringReplicationFactor, backendsPerKey))

rootCmd := newRootCmd()
registerVersionCmd(rootCmd)
registerServeCmd(rootCmd)
registerMigrateCmd(rootCmd)
registerHeadCmd(rootCmd)
registerDeveloperServiceCmd(rootCmd)
registerTestserverCmd(rootCmd)

// Enable consistent hashring gRPC load balancer
balancer.Register(consistentbalancer.NewConsistentHashringBuilder(
xxhash.Sum64,
hashringReplicationFactor,
backendsPerKey,
))

// Create a root command
rootCmd := root.NewCommand()
root.RegisterFlags(rootCmd)

// Add a version command
versionCmd := version.NewCommand(rootCmd.Use)
version.RegisterVersionFlags(versionCmd)
rootCmd.AddCommand(versionCmd)

// Add migration commands
migrateCmd := migrate.NewMigrateCommand(rootCmd.Use)
migrate.RegisterMigrateFlags(migrateCmd)
rootCmd.AddCommand(migrateCmd)

headCmd := migrate.NewHeadCommand(rootCmd.Use)
migrate.RegisterHeadFlags(headCmd)
rootCmd.AddCommand(headCmd)

// Add server commands
var dsConfig cmdutil.DatastoreConfig
serveCmd := serve.NewServeCommand(rootCmd.Use, &dsConfig)
serve.RegisterServeFlags(serveCmd, &dsConfig)
rootCmd.AddCommand(serveCmd)

devtoolsCmd := serve.NewDevtoolsCommand(rootCmd.Use)
serve.RegisterDevtoolsFlags(devtoolsCmd)
rootCmd.AddCommand(devtoolsCmd)

testingCmd := serve.NewTestingCommand(rootCmd.Use)
serve.RegisterTestingFlags(testingCmd)
rootCmd.AddCommand(testingCmd)

_ = rootCmd.Execute()
}
File renamed without changes.
22 changes: 0 additions & 22 deletions cmd/spicedb/version.go

This file was deleted.

29 changes: 29 additions & 0 deletions pkg/cmd/defaults.go
@@ -0,0 +1,29 @@
package cmd

import (
"net/http"
"net/http/pprof"

"github.com/jzelinskie/cobrautil"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog"
)

func DefaultPreRunE(programName string) cobrautil.CobraRunFunc {
return cobrautil.CommandStack(
cobrautil.SyncViperPreRunE(programName),
cobrautil.ZeroLogPreRunE("log", zerolog.InfoLevel),
cobrautil.OpenTelemetryPreRunE("otel", zerolog.InfoLevel),
)
}

func MetricsHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return mux
}
61 changes: 32 additions & 29 deletions cmd/spicedb/migrate.go → pkg/cmd/migrate/migrate.go
@@ -1,4 +1,4 @@
package main
package migrate

import (
"fmt"
Expand All @@ -10,23 +10,24 @@ import (

crdbmigrations "github.com/authzed/spicedb/internal/datastore/crdb/migrations"
"github.com/authzed/spicedb/internal/datastore/postgres/migrations"
cmdutil "github.com/authzed/spicedb/pkg/cmd"
"github.com/authzed/spicedb/pkg/migrate"
)

func registerMigrateCmd(rootCmd *cobra.Command) {
migrateCmd := &cobra.Command{
func RegisterMigrateFlags(cmd *cobra.Command) {
cmd.Flags().String("datastore-engine", "memory", `type of datastore to initialize ("memory", "postgres", "cockroachdb")`)
cmd.Flags().String("datastore-conn-uri", "", `connection string used by remote datastores (e.g. "postgres://postgres:password@localhost:5432/spicedb")`)
}

func NewMigrateCommand(programName string) *cobra.Command {
return &cobra.Command{
Use: "migrate [revision]",
Short: "execute datastore schema migrations",
Long: fmt.Sprintf("Executes datastore schema migrations for the datastore.\nThe special value \"%s\" can be used to migrate to the latest revision.", color.YellowString(migrate.Head)),
PreRunE: defaultPreRunE,
PreRunE: cmdutil.DefaultPreRunE(programName),
Run: migrateRun,
Args: cobra.ExactArgs(1),
}

migrateCmd.Flags().String("datastore-engine", "memory", `type of datastore to initialize ("memory", "postgres", "cockroachdb")`)
migrateCmd.Flags().String("datastore-conn-uri", "", `connection string used by remote datastores (e.g. "postgres://postgres:password@localhost:5432/spicedb")`)

rootCmd.AddCommand(migrateCmd)
}

func migrateRun(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -67,33 +68,35 @@ func migrateRun(cmd *cobra.Command, args []string) {
}
}

func registerHeadCmd(rootCmd *cobra.Command) {
headCmd := &cobra.Command{
Use: "head",
Short: "compute the head database migration revision",
Run: headRevisionRun,
Args: cobra.ExactArgs(0),
}

headCmd.Flags().String("datastore-engine", "postgres", "type of datastore to initialize (e.g. postgres, cockroachdb, memory")
func RegisterHeadFlags(cmd *cobra.Command) {
cmd.Flags().String("datastore-engine", "postgres", "type of datastore to initialize (e.g. postgres, cockroachdb, memory")
}

rootCmd.AddCommand(headCmd)
func NewHeadCommand(programName string) *cobra.Command {
return &cobra.Command{
Use: "head",
Short: "compute the head database migration revision",
PreRunE: cmdutil.DefaultPreRunE(programName),
Run: headRevisionRun,
Args: cobra.ExactArgs(0),
}
}

func headRevisionRun(cmd *cobra.Command, args []string) {
datastoreEngine := cobrautil.MustGetStringExpanded(cmd, "datastore-engine")

var headRevision string
var err error

if datastoreEngine == "cockroachdb" {
var (
engine = cobrautil.MustGetStringExpanded(cmd, "datastore-engine")
headRevision string
err error
)

switch engine {
case "cockroachdb":
headRevision, err = crdbmigrations.CRDBMigrations.HeadRevision()
} else if datastoreEngine == "postgres" {
case "postgres":
headRevision, err = migrations.DatabaseMigrations.HeadRevision()
} else {
log.Fatal().Str("datastore-engine", datastoreEngine).Msg("cannot migrate datastore engine type")
default:
log.Fatal().Str("engine", engine).Msg("cannot migrate datastore engine type")
}

if err != nil {
log.Fatal().Err(err).Msg("unable to compute head revision")
}
Expand Down
16 changes: 8 additions & 8 deletions cmd/spicedb/root.go → pkg/cmd/root/root.go
@@ -1,4 +1,4 @@
package main
package root

import (
"fmt"
Expand All @@ -8,8 +8,13 @@ import (
"github.com/spf13/cobra"
)

func newRootCmd() *cobra.Command {
rootCmd := &cobra.Command{
func RegisterFlags(cmd *cobra.Command) {
cobrautil.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
cobrautil.RegisterOpenTelemetryFlags(cmd.PersistentFlags(), "otel", cmd.Use)
}

func NewCommand() *cobra.Command {
return &cobra.Command{
Use: "spicedb",
Short: "A modern permissions database",
Long: "A database that stores, computes, and validates application permissions",
Expand All @@ -28,9 +33,4 @@ func newRootCmd() *cobra.Command {
color.CyanString("In-memory integration test server"),
),
}

cobrautil.RegisterZeroLogFlags(rootCmd.PersistentFlags(), "log")
cobrautil.RegisterOpenTelemetryFlags(rootCmd.PersistentFlags(), "otel", rootCmd.Use)

return rootCmd
}
43 changes: 22 additions & 21 deletions cmd/spicedb/developer.go → pkg/cmd/serve/devtools.go
@@ -1,4 +1,4 @@
package main
package serve

import (
"context"
Expand All @@ -25,34 +25,35 @@ import (
"google.golang.org/grpc/reflection"

v0svc "github.com/authzed/spicedb/internal/services/v0"
cmdutil "github.com/authzed/spicedb/pkg/cmd"
)

func registerDeveloperServiceCmd(rootCmd *cobra.Command) {
developerServiceCmd := &cobra.Command{
func RegisterDevtoolsFlags(cmd *cobra.Command) {
cobrautil.RegisterGrpcServerFlags(cmd.Flags(), "grpc", "gRPC", ":50051", true)
cobrautil.RegisterHttpServerFlags(cmd.Flags(), "metrics", "metrics", ":9090", true)
cobrautil.RegisterHttpServerFlags(cmd.Flags(), "http", "download", ":8443", false)

cmd.Flags().String("share-store", "inmemory", "kind of share store to use")
cmd.Flags().String("share-store-salt", "", "salt for share store hashing")
cmd.Flags().String("s3-access-key", "", "s3 access key for s3 share store")
cmd.Flags().String("s3-secret-key", "", "s3 secret key for s3 share store")
cmd.Flags().String("s3-bucket", "", "s3 bucket name for s3 share store")
cmd.Flags().String("s3-endpoint", "", "s3 endpoint for s3 share store")
cmd.Flags().String("s3-region", "auto", "s3 region for s3 share store")
}

func NewDevtoolsCommand(programName string) *cobra.Command {
return &cobra.Command{
Use: "serve-devtools",
Short: "runs the developer tools service",
Long: "Serves the authzed.api.v0.DeveloperService which is used for development tooling such as the Authzed Playground",
PreRunE: defaultPreRunE,
Run: developerServiceRun,
PreRunE: cmdutil.DefaultPreRunE(programName),
Run: runfunc,
Args: cobra.ExactArgs(0),
}

cobrautil.RegisterGrpcServerFlags(developerServiceCmd.Flags(), "grpc", "gRPC", ":50051", true)
cobrautil.RegisterHttpServerFlags(developerServiceCmd.Flags(), "metrics", "metrics", ":9090", true)
cobrautil.RegisterHttpServerFlags(developerServiceCmd.Flags(), "http", "download", ":8443", false)

developerServiceCmd.Flags().String("share-store", "inmemory", "kind of share store to use")
developerServiceCmd.Flags().String("share-store-salt", "", "salt for share store hashing")
developerServiceCmd.Flags().String("s3-access-key", "", "s3 access key for s3 share store")
developerServiceCmd.Flags().String("s3-secret-key", "", "s3 secret key for s3 share store")
developerServiceCmd.Flags().String("s3-bucket", "", "s3 bucket name for s3 share store")
developerServiceCmd.Flags().String("s3-endpoint", "", "s3 endpoint for s3 share store")
developerServiceCmd.Flags().String("s3-region", "auto", "s3 region for s3 share store")

rootCmd.AddCommand(developerServiceCmd)
}

func developerServiceRun(cmd *cobra.Command, args []string) {
func runfunc(cmd *cobra.Command, args []string) {
grpcServer, err := cobrautil.GrpcServerFromFlags(cmd, "grpc", grpc.ChainUnaryInterceptor(
grpclog.UnaryServerInterceptor(grpczerolog.InterceptorLogger(log.Logger)),
otelgrpc.UnaryServerInterceptor(),
Expand All @@ -77,7 +78,7 @@ func developerServiceRun(cmd *cobra.Command, args []string) {

// Start the metrics endpoint.
metricsSrv := cobrautil.HttpServerFromFlags(cmd, "metrics")
metricsSrv.Handler = metricsHandler()
metricsSrv.Handler = cmdutil.MetricsHandler()
go func() {
if err := cobrautil.HttpListenFromFlags(cmd, "metrics", metricsSrv, zerolog.InfoLevel); err != nil {
log.Fatal().Err(err).Msg("failed while serving metrics")
Expand Down

0 comments on commit f94d8a4

Please sign in to comment.