clapp

package module
v0.0.0-...-5421dc8 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2021 License: MIT Imports: 11 Imported by: 1

README

clapp

Clapp was built to make it quicker to bootstrap a golang application that runs on command line. It is very opinionated, using specific packages for certain features which may or may not fit needs of different individuals.


Why?

The main drivers for building this were the repetition of adding the following components to command line applications:

  • logger
  • filesystem abstraction
  • configuration loader
Logger

It should be pretty obvious why a logger is a common requirement for any app, not just a command line app. In this case I wanted a simple way to pass a logger around the application.

The requirements for the logger were:

  • that it allowed additional contextual information to be added to log messages
  • allowed logging at different levels which could be toggled easily for debugging purposes
  • a simple interface
  • efficient performance

For this reasons the choice was zerolog.

As mentioned previously this is opinionated and there are many options that would satisfy the above criteria (e.g. zaplog, logrus).

The final choice for zerolog came down to personal preference.

Filesystem abstraction

Go provides some pretty simple ways to interact with the local filesystem, so why use an abstration on top of it? I chose to add an abstraction layer in order to allow us to switch out the underlying implementation, with little effort, if required; currently we utilise this during automated testing.

Afero provides a nice abstraction for working with the filesystem, coming with an in memory driver out of the box.

Configuration loader

When building any tool, portability makes life alot easier. Depending on where/how you plan to use the CLI tool, there are various methods of injecting configuration which are more convenient. When running in a container, typically environment variables are simplest. But when running as a system daemon it may be more convenient to load configuration from a file somewhere on the host. When trying to debug a tool, it can be more convenient to pass values via command line flags.

Bearing this in mind I typically end up creating a three tier configuration system, firstly loading a configuration file, then overriding values from the file with values from the environment, then finally overriding these with any flags passed to the command.

Thats what this provides, albeit still with some effort, more simply than rewriting this every time. A struct can be provided to store the configuration, then using the envconfig library, any values in the struct will be overridden (provided the envconfig tags exist), then pointers to the values from the struct can be passed as flags for each command.

They will be loaded using the following order of precedence (top is highest precedence):

  1. flags
  2. env vars
  3. config file

Viper

Considering we're using a couple of tools from spf13 already (cobra, and afero), you may be wondering why not use viper. I initially planned to use viper, but came across issues when loading arrays from yaml. It would load the yaml array [1, 2, 3] as a string with the value of [1 2 3]. This proved to be an issue with the yaml v2 library, so I opted to load the config file manually using yaml v3, then override with envconfig.


How?

How to use this library is best explained with an example, although it is very similar to cobra itself, I have built a layer of abstraction on top of this.

The easiest way to show what this package does is with an example. You can pull down this repository and run make runtime (you will need docker available on your host), and run some of the examples provided in ./example/main.go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCannotUseNonPointerValue error = errors.New("cannot use non-pointer value")
View Source
var ErrConfigMustBeAPointer error = errors.New("cannot use non-pointer value for config")
View Source
var ErrConfigMustPointToAStruct error = errors.New("cannot point to non-struct value for config")
View Source
var ErrConfigNotFound error = errors.New("config file does not exist")
View Source
var ErrInvalidLogFormat error = errors.New("log format must be one of: console, json")
View Source
var ErrLogFormatMustBeString error = errors.New("log format type in config struct must be string")
View Source
var ErrLogLevelMustBeInt error = errors.New("log level type in config struct must be int")

Functions

func ConfigFromContext

func ConfigFromContext(ctx context.Context) interface{}

func FileMustExistOpt

func FileMustExistOpt() configOpt

func FilePathOpt

func FilePathOpt(path string) configOpt

func FsFromContext

func FsFromContext(ctx context.Context) afero.Fs

func LoggerFromContext

func LoggerFromContext(ctx context.Context) zerolog.Logger

func Run

func Run(a App, e Executor) error

func UpdateLoggerConfigPreRun

func UpdateLoggerConfigPreRun(ctx context.Context) error

Types

type App

type App struct {
	Config          interface{}
	ConfigPath      string
	ConfigMustExist bool
	InitialContext  context.Context
	Fs              afero.Fs
	Logger          zerolog.Logger
	RootCommand     Command
}

type CobraExecutor

type CobraExecutor struct {
	// contains filtered or unexported fields
}

func NewCobraExecutor

func NewCobraExecutor() *CobraExecutor

func (CobraExecutor) Run

func (e CobraExecutor) Run(c Command, ctx context.Context, cfg interface{}) error

type Command

type Command struct {
	Name                string
	Descriptions        Descriptions
	LocalFlags          []Flag
	PersistentFlags     []Flag
	Handle              HandlerFunc
	CustomConfiguration func(*cobra.Command)
	Children            []Command
}

type Config

type Config struct {
	// contains filtered or unexported fields
}

func (*Config) OverrideWithEnvVars

func (c *Config) OverrideWithEnvVars(cfg interface{}) error

type ContextKey

type ContextKey string
const ConfigContextKey ContextKey = "APP_CONFIG"
const ConfigManagerContextKey ContextKey = "CONFIG_MANAGER"
const FsContextKey ContextKey = "FILESYSTEM"
const LogManagerContextKey ContextKey = "LOG_MANAGER"

type Descriptions

type Descriptions struct {
	Long  string
	Short string
}

type ErrCommandNotCastable

type ErrCommandNotCastable struct {
	CastingTo string
}

func (ErrCommandNotCastable) Error

func (e ErrCommandNotCastable) Error() string

type ErrCouldNotBuildRequiredCommandimplementation

type ErrCouldNotBuildRequiredCommandimplementation struct {
	// contains filtered or unexported fields
}

func (ErrCouldNotBuildRequiredCommandimplementation) Error

type ErrFlagTypeNotImplemented

type ErrFlagTypeNotImplemented struct {
	// contains filtered or unexported fields
}

func (ErrFlagTypeNotImplemented) Error

type ErrIncorrectInitialValueForFlag

type ErrIncorrectInitialValueForFlag struct {
	// contains filtered or unexported fields
}

func (ErrIncorrectInitialValueForFlag) Error

type ErrIncorrectValueRefForFlag

type ErrIncorrectValueRefForFlag struct {
	// contains filtered or unexported fields
}

func (ErrIncorrectValueRefForFlag) Error

type ErrOverridingConfigWithEnvFailed

type ErrOverridingConfigWithEnvFailed struct {
	// contains filtered or unexported fields
}

func (ErrOverridingConfigWithEnvFailed) Error

type ErrReadingFile

type ErrReadingFile struct {
	// contains filtered or unexported fields
}

func (ErrReadingFile) Error

func (e ErrReadingFile) Error() string

type ErrUnmarshallingYAML

type ErrUnmarshallingYAML struct {
	// contains filtered or unexported fields
}

func (ErrUnmarshallingYAML) Error

func (e ErrUnmarshallingYAML) Error() string

type Executor

type Executor interface {
	Run(c Command, ctx context.Context, cfg interface{}) error
}

type Flag

type Flag struct {
	Name        string
	Short       string
	Description string
	ValueRef    interface{}
	Type        ValueType
	Required    bool
}

type HandlerFunc

type HandlerFunc func(*cobra.Command, []string) error

type LogManager

type LogManager struct {
	// contains filtered or unexported fields
}

func LogManagerFromContext

func LogManagerFromContext(ctx context.Context) *LogManager

func (*LogManager) ChangeLevel

func (lm *LogManager) ChangeLevel(l int)

func (*LogManager) ChangeOutput

func (lm *LogManager) ChangeOutput(f string) error

type ValueType

type ValueType string
const BoolFlag ValueType = "bool"
const IntFlag ValueType = "int"
const IntSliceFlag ValueType = "intslice"
const StringFlag ValueType = "string"
const StringSliceFlag ValueType = "stringslice"

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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