add app config and logging interface

This commit is contained in:
Alex Goodman
2020-05-21 15:25:59 -04:00
parent 9676f99b86
commit 3e71315195
16 changed files with 607 additions and 64 deletions
+164
View File
@@ -0,0 +1,164 @@
package config
import (
"fmt"
"path"
"strings"
"github.com/adrg/xdg"
"github.com/anchore/imgbom/imgbom/presenter"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/imgbom/internal"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"go.uber.org/zap/zapcore"
)
type CliOnlyOptions struct {
ConfigPath string
Verbosity int
}
type Application struct {
configPath string
PresenterOpt presenter.Option
Presenter string `mapstructure:"output"`
ScopeOpt scope.Option
Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"`
Log Logging `mapstructure:"log"`
CliOptions CliOnlyOptions
}
type Logging struct {
FormatAsJSON bool `mapstructure:"structured"`
LevelOpt zapcore.Level
Level string `mapstructure:"level"`
FileLocation string `mapstructure:"file"`
}
func setNonCliDefaultValues(v *viper.Viper) {
v.SetDefault("log.level", "")
v.SetDefault("log.file", "")
v.SetDefault("log.structured", false)
}
func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application, error) {
// the user may not have a config, and this is OK, we can use the default config + default cobra cli values instead
setNonCliDefaultValues(v)
if cliOpts != nil {
_ = readConfig(v, cliOpts.ConfigPath)
} else {
_ = readConfig(v, "")
}
config := &Application{
CliOptions: *cliOpts,
}
err := v.Unmarshal(config)
if err != nil {
return nil, fmt.Errorf("unable to parse config: %w", err)
}
config.configPath = v.ConfigFileUsed()
err = config.Build()
if err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return config, nil
}
func (cfg *Application) Build() error {
// set the presenter
presenterOption := presenter.ParseOption(cfg.Presenter)
if presenterOption == presenter.UnknownPresenter {
return fmt.Errorf("bad --output value '%s'", cfg.Presenter)
}
cfg.PresenterOpt = presenterOption
// set the scope
scopeOption := scope.ParseOption(cfg.Scope)
if scopeOption == scope.UnknownScope {
return fmt.Errorf("bad --scope value '%s'", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
if cfg.Quiet {
// quiet option trumps all other logging options
cfg.Log.LevelOpt = zapcore.PanicLevel
} else {
if cfg.Log.Level != "" {
if cfg.CliOptions.Verbosity > 0 {
return fmt.Errorf("cannot explicitly set log level (cfg file or env var) and use -v flag together")
}
// set the log level explicitly
err := cfg.Log.LevelOpt.Set(cfg.Log.Level)
if err != nil {
return fmt.Errorf("bad log level value '%s': %+v", cfg.Log.Level, err)
}
} else {
// set the log level implicitly
switch cfg.CliOptions.Verbosity {
case 1:
cfg.Log.LevelOpt = zapcore.InfoLevel
case 2:
cfg.Log.LevelOpt = zapcore.DebugLevel
default:
cfg.Log.LevelOpt = zapcore.ErrorLevel
}
}
}
return nil
}
func readConfig(v *viper.Viper, configPath string) error {
v.AutomaticEnv()
v.SetEnvPrefix(internal.ApplicationName)
// allow for nested options to be specified via environment variables
// e.g. pod.context = APPNAME_POD_CONTEXT
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// use explicitly the given user config
if configPath != "" {
v.SetConfigFile(configPath)
if err := v.ReadInConfig(); err == nil {
return nil
}
// don't fall through to other options if this fails
return fmt.Errorf("unable to read config: %v", configPath)
}
// start searching for valid configs in order...
// 1. look for .<appname>/config.yaml (in the current directory)
v.AddConfigPath("." + internal.ApplicationName)
v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil {
return nil
}
// 2. look for ~/.<appname>.yaml
home, err := homedir.Dir()
if err == nil {
v.AddConfigPath(home)
v.SetConfigName("." + internal.ApplicationName)
if err := v.ReadInConfig(); err == nil {
return nil
}
}
// 3. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
for _, dir := range xdg.ConfigDirs {
v.AddConfigPath(path.Join(dir, internal.ApplicationName))
}
v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil {
return nil
}
return fmt.Errorf("application config not found")
}
+4
View File
@@ -0,0 +1,4 @@
package internal
// note: must be a single word, all lowercase
const ApplicationName = "imgbom"
+11
View File
@@ -0,0 +1,11 @@
package logger
var Log Logger
func init() {
SetLogger(&nopLogger{})
}
func SetLogger(logger Logger) {
Log = logger
}
+34
View File
@@ -0,0 +1,34 @@
package logger
type Logger interface {
Errorf(format string, args ...interface{})
Infof(format string, args ...interface{})
Info(args ...interface{})
Debugf(format string, args ...interface{})
Debug(args ...interface{})
// WithFields(map[string]interface{}) Logger
}
func Errorf(format string, args ...interface{}) {
Log.Errorf(format, args...)
}
func Infof(format string, args ...interface{}) {
Log.Infof(format, args...)
}
func Info(args ...interface{}) {
Log.Info(args...)
}
func Debugf(format string, args ...interface{}) {
Log.Debugf(format, args...)
}
func Debug(args ...interface{}) {
Log.Debug(args...)
}
// func WithFields(fields map[string]interface{}) Logger {
// return Log.WithFields(fields)
// }
+11
View File
@@ -0,0 +1,11 @@
package logger
type nopLogger struct{}
func (l *nopLogger) Errorf(format string, args ...interface{}) {}
func (l *nopLogger) Infof(format string, args ...interface{}) {}
func (l *nopLogger) Info(args ...interface{}) {}
func (l *nopLogger) Debugf(format string, args ...interface{}) {}
func (l *nopLogger) Debug(args ...interface{}) {}
// func (l *nopLogger) WithFields(fields map[string]interface{}) Logger { return &nopLogger{} }