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

Export CLI commands as a library #325

Merged
merged 8 commits into from Dec 8, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
}
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