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
+46
View File
@@ -0,0 +1,46 @@
package cmd
import (
"fmt"
"github.com/anchore/imgbom/imgbom/presenter"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/imgbom/internal/config"
"github.com/spf13/viper"
)
var cliOpts = config.CliOnlyOptions{}
func setCliOptions() {
rootCmd.PersistentFlags().StringVarP(&cliOpts.ConfigPath, "config", "c", "", "application config file")
// scan options
flag := "scope"
rootCmd.Flags().StringP(
"scope", "s", scope.AllLayersScope.String(),
fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options))
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
}
// output & formatting options
flag = "output"
rootCmd.Flags().StringP(
flag, "o", presenter.JSONPresenter.String(),
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
}
flag = "quiet"
rootCmd.Flags().BoolP(
flag, "q", false,
"suppress all auxiliary output",
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
}
rootCmd.Flags().CountVarP(&cliOpts.Verbosity, "verbose", "v", "increase verbosity (-v, -vv, -vvv ...)")
}
+20
View File
@@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"os"
"github.com/anchore/imgbom/internal/config"
"github.com/spf13/viper"
)
var appConfig *config.Application
func loadAppConfig() {
cfg, err := config.LoadConfigFromFile(viper.GetViper(), &cliOpts)
if err != nil {
fmt.Printf("failed to load application config: \n\t%+v\n", err)
os.Exit(1)
}
appConfig = cfg
}
+122
View File
@@ -0,0 +1,122 @@
package cmd
import (
"os"
"github.com/anchore/imgbom/imgbom"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type logConfig struct {
EnableConsole bool
EnableFile bool
FormatAsJSON bool
Level zapcore.Level
FileLocation string
}
type zapLogger struct {
sugaredLogger *zap.SugaredLogger
}
func setupLoggingFromAppConfig() {
setupLogging(
logConfig{
EnableConsole: appConfig.Log.FileLocation == "" && !appConfig.Quiet,
EnableFile: appConfig.Log.FileLocation != "",
Level: appConfig.Log.LevelOpt,
FormatAsJSON: appConfig.Log.FormatAsJSON,
FileLocation: appConfig.Log.FileLocation,
},
)
}
func setupLogging(config logConfig) {
imgbom.SetLogger(newZapLogger(config))
}
func getConsoleEncoder(config logConfig) zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
if config.FormatAsJSON {
return zapcore.NewJSONEncoder(encoderConfig)
}
encoderConfig.EncodeTime = nil
encoderConfig.EncodeCaller = nil
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getFileEncoder(config logConfig) zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
if config.FormatAsJSON {
return zapcore.NewJSONEncoder(encoderConfig)
}
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter(location string) zapcore.WriteSyncer {
file, _ := os.Create(location)
return zapcore.AddSync(file)
}
func newZapLogger(config logConfig) *zapLogger {
cores := []zapcore.Core{}
if config.EnableConsole {
// note: the report should go to stdout, all logs should go to stderr
writer := zapcore.Lock(os.Stderr)
core := zapcore.NewCore(getConsoleEncoder(config), writer, config.Level)
cores = append(cores, core)
}
if config.EnableFile {
writer := zapcore.AddSync(getLogWriter(config.FileLocation))
core := zapcore.NewCore(getFileEncoder(config), writer, config.Level)
cores = append(cores, core)
}
combinedCore := zapcore.NewTee(cores...)
// AddCallerSkip skips 2 number of callers, this is important else the file that gets
// logged will always be the wrapped file (In our case logger.go)
logger := zap.New(combinedCore,
zap.AddCallerSkip(2),
zap.AddCaller(),
).Sugar()
return &zapLogger{
sugaredLogger: logger,
}
}
func (l *zapLogger) Debugf(format string, args ...interface{}) {
l.sugaredLogger.Debugf(format, args...)
}
func (l *zapLogger) Infof(format string, args ...interface{}) {
l.sugaredLogger.Infof(format, args...)
}
func (l *zapLogger) Debug(args ...interface{}) {
l.sugaredLogger.Debug(args...)
}
func (l *zapLogger) Info(args ...interface{}) {
l.sugaredLogger.Info(args...)
}
func (l *zapLogger) Errorf(format string, args ...interface{}) {
l.sugaredLogger.Errorf(format, args...)
}
// func (l *zapLogger) WithFields(fields map[string]interface{}) logger.Logger {
// var f = make([]interface{}, 0)
// for k, v := range fields {
// f = append(f, k)
// f = append(f, v)
// }
// newLogger := l.sugaredLogger.With(f...)
// return &zapLogger{newLogger}
// }
+32 -51
View File
@@ -1,27 +1,20 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/anchore/imgbom/imgbom"
"github.com/anchore/imgbom/imgbom/presenter"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/imgbom/internal"
"github.com/anchore/imgbom/internal/logger"
"github.com/anchore/stereoscope"
"github.com/spf13/cobra"
)
const ApplicationName = "imgbom"
var rootOptions struct {
cfgFile string
scope string
output string
}
var rootCmd = &cobra.Command{
Use: fmt.Sprintf("%s [IMAGE]", ApplicationName),
Use: fmt.Sprintf("%s [IMAGE]", internal.ApplicationName),
Short: "A container image BOM tool", // TODO: add copy
Long: internal.Tprintf(`\
Supports the following image sources:
@@ -29,66 +22,54 @@ Supports the following image sources:
{{.appName}} docker://yourrepo/yourimage:tag explicitly use the docker daemon
{{.appName}} tar://path/to/yourimage.tar use a tarball from disk
`, map[string]interface{}{
"appName": ApplicationName,
"appName": internal.ApplicationName,
}),
Args: cobra.MaximumNArgs(1),
Run: doRunCmd,
}
func init() {
setCliOptions()
cobra.OnInitialize(loadAppConfig)
cobra.OnInitialize(setupLoggingFromAppConfig)
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
logger.Errorf("could not start application: %w", err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(loadApplicationConfig)
// TODO: add config support
//rootCmd.PersistentFlags().StringVarP(&rootOptions.cfgFile, "config", "c", "", "config file")
// scan options
rootCmd.Flags().StringVarP(&rootOptions.scope, "scope", "s", scope.AllLayersScope.String(),
fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options))
// output & formatting options
rootCmd.Flags().StringVarP(&rootOptions.output, "output", "o", presenter.JSONOption.String(),
fmt.Sprintf("report output formatter, options=%v", presenter.Options))
}
func loadApplicationConfig() {
// TODO: add config support
}
func doRunCmd(cmd *cobra.Command, args []string) {
var pres = presenter.GetPresenter(rootOptions.output)
if pres == nil {
// TODO: replace with log and exit
panic("could not determine presenter")
}
scopeOption := scope.ParseOption(rootOptions.scope)
if scopeOption == scope.UnknownScope {
// TODO: replace with log and exit
panic(scopeOption)
}
img, err := stereoscope.GetImage(args[0])
appCfgStr, err := json.MarshalIndent(&appConfig, " ", " ")
if err != nil {
// TODO: replace with log and exit
panic(err)
logger.Debugf("could not display application config: %+v", err)
} else {
logger.Debugf("application config:\n%+v", string(appCfgStr))
}
userImageStr := args[0]
logger.Infof("fetching image %s...", userImageStr)
img, err := stereoscope.GetImage(userImageStr)
if err != nil {
logger.Errorf("could not fetch image '%s': %w", userImageStr, err)
os.Exit(1)
}
defer stereoscope.Cleanup()
catalog, err := imgbom.CatalogImage(img, scopeOption)
logger.Info("cataloging image...")
catalog, err := imgbom.CatalogImage(img, appConfig.ScopeOpt)
if err != nil {
// TODO: replace with log and exit
panic(err)
logger.Errorf("could not catalog image: %w", err)
os.Exit(1)
}
err = pres.Present(os.Stdout, img, catalog)
logger.Info("done!")
err = presenter.GetPresenter(appConfig.PresenterOpt).Present(os.Stdout, img, catalog)
if err != nil {
// TODO: replace with log and exit
panic(err)
logger.Errorf("could not format catalog results: %w", err)
os.Exit(1)
}
}
+2 -1
View File
@@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/anchore/imgbom/internal"
"github.com/spf13/cobra"
)
@@ -29,5 +30,5 @@ func SetVersion(v *Version) {
}
func printVersion(cmd *cobra.Command, args []string) {
fmt.Printf("%s %s\n", ApplicationName, version.Version)
fmt.Printf("%s %s\n", internal.ApplicationName, version.Version)
}