mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 15:06:36 +01:00
add app config and logging interface
This commit is contained in:
+46
@@ -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 ...)")
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user