mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
add app config and logging interface
This commit is contained in:
@@ -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")
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package internal
|
||||
|
||||
// note: must be a single word, all lowercase
|
||||
const ApplicationName = "imgbom"
|
||||
@@ -0,0 +1,11 @@
|
||||
package logger
|
||||
|
||||
var Log Logger
|
||||
|
||||
func init() {
|
||||
SetLogger(&nopLogger{})
|
||||
}
|
||||
|
||||
func SetLogger(logger Logger) {
|
||||
Log = logger
|
||||
}
|
||||
@@ -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)
|
||||
// }
|
||||
@@ -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{} }
|
||||
Reference in New Issue
Block a user