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
- Variables
- func ContextWithDelay(in context.Context, delay time.Duration) context.Context
- func ContextWithValuesFrom(value context.Context) context.Context
- func Exit(code int)
- func HandleExit()
- func Must(err error)
- func New(use, desc string, options ...Option) *cobra.Command
- func NewVersionCommand() *cobra.Command
- func SignalContext(ctx context.Context, signals ...os.Signal) context.Context
- func SignalRootContext() context.Context
- type LoggerOption
- type Option
- func WithLogToGraylog() Option
- func WithLogToGraylogHostname(hostname string) Option
- func WithLogVerboseFlag() Option
- func WithRun(run RunFuncWithContext) Option
- func WithRunner(runner Runner) Option
- func WithSubCommand(sub *cobra.Command) Option
- func WithVersionCommand() Option
- func WithVersionLog(level logrus.Level) Option
- type RunFunc
- type RunFuncWithContext
- type Runner
Constants ¶
const ( ExitCodeOK = 0 ExitCodeGeneralError = 1 ExitCodeUsage = 2 ExitCodeSDK = 16 ExitCodeCustom = 32 ExitCodeMultipleInterrupts = ExitCodeSDK + 0 )
Variables ¶
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 ¶
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 ¶
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.
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 ¶
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.
func NewVersionCommand ¶
NewVersionCommand creates a Cobra command, which prints the version and other build parameters (see Build* variables) and exits.
func SignalContext ¶
SignalContext returns a copy of the parent context that gets cancelled if the application gets any of the given signals.
func SignalRootContext ¶
SignalRootContext returns a new empty context, that gets canneld on SIGINT or SIGTEM.
Types ¶
type LoggerOption ¶
type Option ¶
func WithLogToGraylog ¶
func WithLogToGraylog() 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 ¶
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 WithVersionCommand ¶
func WithVersionCommand() Option