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

cobrautil v2 #19

Merged
merged 8 commits into from
Oct 3, 2022
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
7 changes: 5 additions & 2 deletions cobrautil.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func SyncViperPreRunE(prefix string) CobraRunFunc {
// CobraRunFunc is the signature of cobra.Command RunFuncs.
type CobraRunFunc func(cmd *cobra.Command, args []string) error

// RunFuncStack chains together a collection of CobraCommandFuncs into one.
// CommandStack chains together a collection of CobraCommandFuncs into one.
func CommandStack(cmdfns ...CobraRunFunc) CobraRunFunc {
return func(cmd *cobra.Command, args []string) error {
for _, cmdfn := range cmdfns {
Expand All @@ -65,7 +65,10 @@ func CommandStack(cmdfns ...CobraRunFunc) CobraRunFunc {
}
}

func prefixJoiner(prefix string) func(...string) string {
// PrefixJoiner joins a list of strings with the "-" separator, including the provided prefix string
//
// example: PrefixJoiner("hi")("how", "are", "you") = "hi-how-are-you"
func PrefixJoiner(prefix string) func(...string) string {
return func(xs ...string) string {
return stringz.Join("-", append([]string{prefix}, xs...)...)
}
Expand Down
17 changes: 9 additions & 8 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,36 @@ import (
"github.com/rs/zerolog"
"github.com/spf13/cobra"

"github.com/jzelinskie/cobrautil"
"github.com/jzelinskie/cobrautil/v2"
zl "github.com/jzelinskie/cobrautil/v2/zerolog"
)

func ExampleCommandStack() {
cmd := &cobra.Command{
Use: "mycmd",
PreRunE: cobrautil.CommandStack(
cobrautil.SyncViperPreRunE("myprogram"),
cobrautil.ZeroLogRunE("log", zerolog.InfoLevel),
zl.RunE("log", zerolog.InfoLevel),
),
}

cobrautil.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
zl.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
}

func ExampleRegisterZeroLogFlags() {
cmd := &cobra.Command{
Use: "mycmd",
PreRunE: cobrautil.ZeroLogRunE("log", zerolog.InfoLevel),
PreRunE: zl.RunE("log", zerolog.InfoLevel),
}

cobrautil.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
zl.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
}

func ExampleZeroLogRunE() {
func ExampleRunE() {
cmd := &cobra.Command{
Use: "mycmd",
PreRunE: cobrautil.ZeroLogRunE("log", zerolog.InfoLevel),
PreRunE: zl.RunE("log", zerolog.InfoLevel),
}

cobrautil.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
zl.RegisterZeroLogFlags(cmd.PersistentFlags(), "log")
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module github.com/jzelinskie/cobrautil
module github.com/jzelinskie/cobrautil/v2

go 1.18

require (
github.com/go-logr/logr v1.2.3
github.com/jzelinskie/stringz v0.0.1
github.com/mattn/go-isatty v0.0.16
github.com/rs/zerolog v1.28.0
Expand All @@ -23,7 +24,6 @@ require (
require (
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
Expand Down
99 changes: 0 additions & 99 deletions grpc.go

This file was deleted.

198 changes: 198 additions & 0 deletions grpc/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package grpc

import (
"fmt"
"net"
"time"

"github.com/jzelinskie/cobrautil/v2"

"github.com/go-logr/logr"
"github.com/jzelinskie/stringz"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
)

// ConfigureFunc is a function used to configure this CobraUtil
type ConfigureFunc = func(cu *CobraUtil)

// New creates a configuration that exposes RegisterFlags and RunE
// to integrate with cobra
func New(serviceName string, configurations ...ConfigureFunc) *CobraUtil {
cu := CobraUtil{
serviceName: stringz.DefaultEmpty(serviceName, "grpc"),
preRunLevel: 0,
logger: logr.Discard(),
defaultAddr: ":50051",
defaultEnabled: false,
flagPrefix: "grpc",
}
for _, configure := range configurations {
configure(&cu)
}
return &cu
}

// CobraUtil carries the configuration for a otel CobraRunFunc
type CobraUtil struct {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd name this type config in each package instead of cobrautil. I think it makes sense to also make the type private, since all of the fields are also private. I'm pretty sure you can still return the type from functions, but folks will just be unable to construct it from a literal in other packages.

Picking up optgen might also be a good idea for this too: https://github.com/ecordell/optgen

flagPrefix string
serviceName string
defaultAddr string
defaultEnabled bool
logger logr.Logger
preRunLevel int
}

// RegisterGrpcServerFlags adds the following flags for use with
// GrpcServerFromFlags:
// - "$PREFIX-addr"
// - "$PREFIX-tls-cert-path"
// - "$PREFIX-tls-key-path"
// - "$PREFIX-max-conn-age"
func RegisterGrpcServerFlags(flags *pflag.FlagSet, flagPrefix, serviceName, defaultAddr string, defaultEnabled bool) {
serviceName = stringz.DefaultEmpty(serviceName, "grpc")
defaultAddr = stringz.DefaultEmpty(defaultAddr, ":50051")
prefixed := cobrautil.PrefixJoiner(stringz.DefaultEmpty(flagPrefix, "grpc"))

flags.String(prefixed("addr"), defaultAddr, "address to listen on to serve "+serviceName)
flags.String(prefixed("network"), "tcp", "network type to serve "+serviceName+` ("tcp", "tcp4", "tcp6", "unix", "unixpacket")`)
flags.String(prefixed("tls-cert-path"), "", "local path to the TLS certificate used to serve "+serviceName)
flags.String(prefixed("tls-key-path"), "", "local path to the TLS key used to serve "+serviceName)
flags.Duration(prefixed("max-conn-age"), 30*time.Second, "how long a connection serving "+serviceName+" should be able to live")
flags.Bool(prefixed("enabled"), defaultEnabled, "enable "+serviceName+" gRPC server")
}

// RegisterGrpcServerFlags adds the following flags for use with
// GrpcServerFromFlags:
// - "$PREFIX-addr"
// - "$PREFIX-tls-cert-path"
// - "$PREFIX-tls-key-path"
// - "$PREFIX-max-conn-age"
func (cu CobraUtil) RegisterGrpcServerFlags(flags *pflag.FlagSet) {
RegisterGrpcServerFlags(flags, cu.flagPrefix, cu.serviceName, cu.defaultAddr, cu.defaultEnabled)
}

// ServerFromFlags creates an *grpc.Server as configured by the flags from
// RegisterGrpcServerFlags().
func ServerFromFlags(cmd *cobra.Command, flagPrefix string, opts ...grpc.ServerOption) (*grpc.Server, error) {
return New("", WithFlagPrefix(flagPrefix)).ServerFromFlags(cmd, opts...)
}

// ServerFromFlags creates an *grpc.Server as configured by the flags from
// RegisterGrpcServerFlags().
func (cu CobraUtil) ServerFromFlags(cmd *cobra.Command, opts ...grpc.ServerOption) (*grpc.Server, error) {
prefixed := cobrautil.PrefixJoiner(cu.flagPrefix)

opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionAge: cobrautil.MustGetDuration(cmd, prefixed("max-conn-age")),
}))

certPath := cobrautil.MustGetStringExpanded(cmd, prefixed("tls-cert-path"))
keyPath := cobrautil.MustGetStringExpanded(cmd, prefixed("tls-key-path"))

switch {
case isInsecure(certPath, keyPath):
return grpc.NewServer(opts...), nil

case isSecure(certPath, keyPath):
creds, err := credentials.NewServerTLSFromFile(certPath, keyPath)
if err != nil {
return nil, err
}
opts = append(opts, grpc.Creds(creds))
return grpc.NewServer(opts...), nil

default:
return nil, fmt.Errorf(
"failed to start gRPC server: must provide both --%s-tls-cert-path and --%s-tls-key-path",
cu.flagPrefix,
cu.flagPrefix,
)
}
}

// ListenFromFlags listens on an gRPC server using the configuration stored
// in the cobra command that was registered with RegisterGrpcServerFlags.
func ListenFromFlags(cmd *cobra.Command, flagPrefix string, srv *grpc.Server, preRunLevel int) error {
return New("", WithPreRunLevel(preRunLevel), WithFlagPrefix(flagPrefix)).ListenFromFlags(cmd, srv)
}

// ListenFromFlags listens on an gRPC server using the configuration stored
// in the cobra command that was registered with RegisterGrpcServerFlags.
func (cu CobraUtil) ListenFromFlags(cmd *cobra.Command, srv *grpc.Server) error {
prefixed := cobrautil.PrefixJoiner(cu.flagPrefix)

if !cobrautil.MustGetBool(cmd, prefixed("enabled")) {
return nil
}

network := cobrautil.MustGetString(cmd, prefixed("network"))
addr := cobrautil.MustGetStringExpanded(cmd, prefixed("addr"))

l, err := net.Listen(network, addr)
if err != nil {
return fmt.Errorf("failed to listen on addr for gRPC server: %w", err)
}

certPath := cobrautil.MustGetStringExpanded(cmd, prefixed("tls-cert-path"))
keyPath := cobrautil.MustGetStringExpanded(cmd, prefixed("tls-key-path"))
cu.logger.V(cu.preRunLevel).Info(
"grpc server started listening",
"addr", addr,
"network", network,
"prefix", cu.flagPrefix,
"insecure", isInsecure(certPath, keyPath))

if err := srv.Serve(l); err != nil {
return fmt.Errorf("failed to serve gRPC: %w", err)
}

return nil
}

// WithLogger defines the logger used to log messages in this package
func WithLogger(logger logr.Logger) ConfigureFunc {
return func(cu *CobraUtil) {
cu.logger = logger
}
}

// WithDefaultAddress defines the default value of the address the server will listen at.
// Defaults to ":50051"
func WithDefaultAddress(addr string) ConfigureFunc {
return func(cu *CobraUtil) {
cu.defaultAddr = addr
}
}

// WithDefaultEnabled defines whether the gRPC server is enabled by default. Defaults to "false".
func WithDefaultEnabled(enabled bool) ConfigureFunc {
return func(cu *CobraUtil) {
cu.defaultEnabled = enabled
}
}

// WithFlagPrefix defines prefix used with the generated flags. Defaults to "grpc".
func WithFlagPrefix(flagPrefix string) ConfigureFunc {
return func(cu *CobraUtil) {
cu.flagPrefix = flagPrefix
}
}

// WithPreRunLevel defines the logging level used for pre-run log messages. Defaults to "debug".
func WithPreRunLevel(preRunLevel int) ConfigureFunc {
return func(cu *CobraUtil) {
cu.preRunLevel = preRunLevel
}
}

func isInsecure(certPath, keyPath string) bool {
return certPath == "" && keyPath == ""
}

func isSecure(certPath, keyPath string) bool {
return certPath != "" && keyPath != ""
}
Loading