cmdutil

package
v8.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2024 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package cmdutil contains helper utilities for setting up a CLI with Go, providing basic application behavior and for reducing boilerplate code.

An example application can be found at https://github.com/rebuy-de/golang-template.

Graceful Application Exits

In many command line applications it is desired to exit the process immediately, if it is clear that the application cannot recover. Important note: This is designed for actual applications (ie not libraries), because only the application itself should decide when to exit. Libraries should alway return errors.

There are three ways to handle fatal errors in Go. With os.Exit() the process will terminate immediately, but it will not call any deferrers which means that possible cleanup task do not get called. The next way is to call panic, which respects the defer statements, but unfortunately it is not possible to define an exit code and the user gets confused with a stack trace. Finally, the function could just return an error indicating that things failed, but this introduces a lot of code, conditionals and appears unnecessary, when it is already clear that the application cannot recover.

The package cmdutil provides an alternative, which panics with a known struct and catches it right before the application exit. This is an example to illustrate the usage:

func main() {
  defer cmdutil.HandleExit()
  run()
}

func run() {
  defer fmt.Println("important cleanup")
  err := doSomething()
  if err != nil {
    log.Error(err)
    cmdutil.Exit(2)
  }
}

The defer of HandleExit is the first statement in the main function. It ensures a pretty output and that the application exits with the specified exit code. The run function does something and makes the application exit with an exit code. The specified defer statement is still called. Also the application logging facility should be used to communicate the error, so the error actually appears on external logging applications like Syslog or Graylog.

Minimal Application Boilerplate

Golang is very helpful for creating glue code in the ops area and creating micro services. But when you want features like proper logging, a version subcommand and a clean structure, there is still a lot of boilerplate code needed. NewRootCommand creates a ready-to-use Cobra command to reduce the necessary code. This is an example to illustrate the usage:

type App struct {
    Name string
}

func (app *App) Run(cmd *cobra.Command, args []string) {
    log.Infof("hello %s", app.Name)
}

func (app *App) Bind(cmd *cobra.Command) {
    cmd.PersistentFlags().StringVarP(
        &app.Name, "name", "n", "world",
        `Your name.`)
}

func NewRootCommand() *cobra.Command {
    cmd := cmdutil.NewRootCommand(new(App))
    cmd.Short = "an example app for golang which can be used as template"
    return cmd
}

The App struct contains fields for parameters which are defined in Bind or for internal states which might get defined while running the application.

NewRootCommand also attaches NewVersionCommand to the application. It prints the compiled version of the application and other build parameters. These values need to be set by the build system via ldflags.

BUILD_XDST=$(pwd)/vendor/github.com/rebuy-de/rebuy-go-sdk/cmdutil
go build -ldflags "\
  -X '${BUILD_XDST}.BuildName=${NAME}' \
  -X '${BUILD_XDST}.BuildPackage=${PACKAGE}' \
  -X '${BUILD_XDST}.BuildVersion=${BUILD_VERSION}' \
  -X '${BUILD_XDST}.BuildDate=${BUILD_DATE}' \
  -X '${BUILD_XDST}.BuildHash=${BUILD_HASH}' \
  -X '${BUILD_XDST}.BuildEnvironment=${BUILD_ENVIRONMENT}' \

Index

Constants

View Source
const (
	ExitCodeOK           = 0
	ExitCodeGeneralError = 1
	ExitCodeUsage        = 2
	ExitCodeSDK          = 16
	ExitCodeCustom       = 32

	ExitCodeMultipleInterrupts = ExitCodeSDK + 0
)

Variables

View Source
var (
	Name       = "unknown"
	Version    = "unknown"
	GoModule   = "unknown"
	GoPackage  = "unknown"
	GoVersion  = "unknown"
	SDKVersion = "unknown"
	BuildDate  = "unknown"
	CommitDate = "unknown"
	CommitHash = "unknown"
)

The Build* variables are used by NewVersionCommand and NewRootCommand. They should be overwritten on build time by using ldflags.

Functions

func ContextWithDelay

func ContextWithDelay(in context.Context, delay time.Duration) context.Context

ContextWithDelay delays the context cancel by the given delay. In the background it creates a new context with ContextWithValuesFrom and cancels it after the original one got canceled.

func ContextWithValuesFrom deprecated

func ContextWithValuesFrom(value context.Context) context.Context

ContextWithValuesFrom creates a new context, but still references the values from the given context. This is helpful if a background context is needed that needs to have the values of an exiting context.

Deprecated: Use context.WithoutCancel instead.

func Exit

func Exit(code int)

Exit causes the current program to exit with the given status code. On the contrary to os.Exit, it respects defer statements. It requires the HandleExit function to be deferred in top of the main function.

Internally this is done by throwing a panic with the ExitCode type, which gets recovered in the HandleExit function.

func HandleExit

func HandleExit()

HandleExit recovers from Exit calls and terminates the current program with a proper exit code. It should get deferred at the beginning of the main function.

func Must deprecated

func Must(err error)

Must exits the application via Exit(1) and logs the error, if err does not equal nil. Additionally it logs the error with `%+v` to the debug log, so it can used together with github.com/pkg/errors to retrive more details about the error.

Deprecated: Bubble the error up to the Runner.Run function and return it there instead. It is still preferable to let the application die, when there is no obvious way of handling it, but in reality this is not often the case and Must is encouraging permature exits.

func New

func New(use, desc string, options ...Option) *cobra.Command

func NewVersionCommand

func NewVersionCommand() *cobra.Command

NewVersionCommand creates a Cobra command, which prints the version and other build parameters (see Build* variables) and exits.

func SignalContext

func SignalContext(ctx context.Context, signals ...os.Signal) context.Context

SignalContext returns a copy of the parent context that gets cancelled if the application gets any of the given signals.

func SignalRootContext

func SignalRootContext() context.Context

SignalRootContext returns a new empty context, that gets canneld on SIGINT or SIGTEM.

Types

type LoggerOption

type LoggerOption struct {
	JSONFormatter bool
	GELFLogger    bool
}

func (*LoggerOption) Bind

func (o *LoggerOption) Bind(cmd *cobra.Command) error

type Option

type Option func(*cobra.Command) error

func WithLogToGraylog

func WithLogToGraylog() Option

func WithLogToGraylogHostname

func WithLogToGraylogHostname(hostname string) Option

func WithLogVerboseFlag

func WithLogVerboseFlag() Option

func WithRun

func WithRun(run RunFuncWithContext) Option

deprecated: Use WithRun instead. It gets replaced, because different subcommands (eg dev and daemon) usually do not share flags. Therefore it is cumbersome to mangle their flags into the same struct. WithRunner allows using separate structs for subcommands.

See examples in https://github.com/rebuy-de/rebuy-go-sdk/pull/147/files for migration demonstration.

func WithRunner

func WithRunner(runner Runner) Option

WithRunner that accepts a generic type which must implement the [Binder] interface. The Bind function gets called with cobra.Command so it can prepare Cobra flags.

func WithSubCommand

func WithSubCommand(sub *cobra.Command) Option

func WithVersionCommand

func WithVersionCommand() Option

func WithVersionLog

func WithVersionLog(level logrus.Level) Option

type RunFunc

type RunFunc func(cmd *cobra.Command, args []string)

type RunFuncWithContext

type RunFuncWithContext func(ctx context.Context, cmd *cobra.Command, args []string)

type Runner

type Runner interface {
	Bind(*cobra.Command) error
	Run(context.Context) error
}

Binder defines the interface used by the generic WithRun function.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL