From 206fd8b7d38b25297919a36432dffb622bd56e7b Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 8 Sep 2020 08:37:40 -0400 Subject: [PATCH] working Signed-off-by: Alex Goodman --- cmd/cmd.go | 2 +- go.mod | 2 +- go.sum | 4 + internal/log/log.go | 8 +- internal/log/nop.go | 1 + internal/logger/hclog_stdlog_adapter.go | 78 ++++++++++ internal/logger/logger_hclog_adapter.go | 148 ++++++++++++++++++ internal/logger/logrus.go | 37 +++-- internal/logger/logrus_hclog_adapter.go | 181 +++++++++++++++++++++++ syft/cataloger/catalog.go | 5 +- syft/cataloger/cataloger.go | 4 +- syft/logger/logger.go | 1 + syft/plugin/config.go | 2 +- syft/plugin/grpc/cataloger_client.go | 6 +- syft/plugin/grpc/file_resolver_client.go | 1 + syft/plugin/grpc/file_resolver_server.go | 5 - syft/plugin/plugin.go | 44 +++--- syft/plugin/repository.go | 21 ++- syft/plugin/type.go | 2 +- 19 files changed, 498 insertions(+), 54 deletions(-) create mode 100644 internal/logger/hclog_stdlog_adapter.go create mode 100644 internal/logger/logger_hclog_adapter.go create mode 100644 internal/logger/logrus_hclog_adapter.go diff --git a/cmd/cmd.go b/cmd/cmd.go index be9328f50..3e6e242b8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -117,7 +117,7 @@ func initLogging() { logWrapper := logger.NewLogrusLogger(cfg) syft.SetLogger(logWrapper) stereoscope.SetLogger(&logger.LogrusNestedLogger{ - Logger: logWrapper.Logger.WithField("from-lib", "steroscope"), + Logger: logWrapper.Logger.WithField("from-lib", "stereoscope"), }) } diff --git a/go.mod b/go.mod index 9337a254f..fedf33d86 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/uuid v1.1.1 github.com/gookit/color v1.2.7 github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect - github.com/hashicorp/go-hclog v0.9.2 + github.com/hashicorp/go-hclog v0.14.1 github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-plugin v1.3.0 github.com/hashicorp/go-version v1.2.0 diff --git a/go.sum b/go.sum index 6f70f4e2e..5e3d33955 100644 --- a/go.sum +++ b/go.sum @@ -477,6 +477,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -597,6 +599,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -1025,6 +1028,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/log/log.go b/internal/log/log.go index acf6ab49e..c06ddfc60 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -1,6 +1,8 @@ package log -import "github.com/anchore/syft/syft/logger" +import ( + "github.com/anchore/syft/syft/logger" +) var Log logger.Logger = &nopLogger{} @@ -8,6 +10,10 @@ func Errorf(format string, args ...interface{}) { Log.Errorf(format, args...) } +func Error(args ...interface{}) { + Log.Error(args...) +} + func Infof(format string, args ...interface{}) { Log.Infof(format, args...) } diff --git a/internal/log/nop.go b/internal/log/nop.go index da5db9cd9..60a44a3eb 100644 --- a/internal/log/nop.go +++ b/internal/log/nop.go @@ -3,6 +3,7 @@ package log type nopLogger struct{} func (l *nopLogger) Errorf(format string, args ...interface{}) {} +func (l *nopLogger) Error(args ...interface{}) {} func (l *nopLogger) Infof(format string, args ...interface{}) {} func (l *nopLogger) Info(args ...interface{}) {} func (l *nopLogger) Debugf(format string, args ...interface{}) {} diff --git a/internal/logger/hclog_stdlog_adapter.go b/internal/logger/hclog_stdlog_adapter.go new file mode 100644 index 000000000..460118840 --- /dev/null +++ b/internal/logger/hclog_stdlog_adapter.go @@ -0,0 +1,78 @@ +package logger + +import ( + "bytes" + "strings" + + "github.com/hashicorp/go-hclog" +) + +// Source: https://github.com/hashicorp/go-hclog/blob/master/stdlog.go (MIT License) + +// Provides a io.Writer to shim the data out of *log.Logger +// and back into our Logger. This is basically the only way to +// build upon *log.Logger. +type HCLogStdlogAdapter struct { + log hclog.Logger + inferLevels bool + forceLevel hclog.Level +} + +// Take the data, infer the levels if configured, and send it through +// a regular Logger. +func (s *HCLogStdlogAdapter) Write(data []byte) (int, error) { + str := string(bytes.TrimRight(data, " \t\n")) + + if s.forceLevel != hclog.NoLevel { + // Use pickLevel to strip log levels included in the line since we are + // forcing the level + _, str := s.pickLevel(str) + + // Log at the forced level + s.dispatch(str, s.forceLevel) + } else if s.inferLevels { + level, str := s.pickLevel(str) + s.dispatch(str, level) + } else { + s.log.Info(str) + } + + return len(data), nil +} + +func (s *HCLogStdlogAdapter) dispatch(str string, level hclog.Level) { + switch level { + case hclog.Trace: + s.log.Trace(str) + case hclog.Debug: + s.log.Debug(str) + case hclog.Info: + s.log.Info(str) + case hclog.Warn: + s.log.Warn(str) + case hclog.Error: + s.log.Error(str) + default: + s.log.Info(str) + } +} + +// Detect, based on conventions, what log level this is. +func (s *HCLogStdlogAdapter) pickLevel(str string) (hclog.Level, string) { + switch { + case strings.HasPrefix(str, "[DEBUG]"): + return hclog.Debug, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[TRACE]"): + return hclog.Trace, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[INFO]"): + return hclog.Info, strings.TrimSpace(str[6:]) + case strings.HasPrefix(str, "[WARN]"): + return hclog.Warn, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERROR]"): + return hclog.Error, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERR]"): + return hclog.Error, strings.TrimSpace(str[5:]) + default: + return hclog.Info, str + } +} diff --git a/internal/logger/logger_hclog_adapter.go b/internal/logger/logger_hclog_adapter.go new file mode 100644 index 000000000..0a85ecc79 --- /dev/null +++ b/internal/logger/logger_hclog_adapter.go @@ -0,0 +1,148 @@ +package logger + +import ( + "io" + "log" + + "github.com/anchore/syft/syft/logger" + + "github.com/hashicorp/go-hclog" +) + +// integrity check +var _ hclog.Logger = &LoggerHCLogAdapter{} + +// LoggerHCLogAdapter is used to (partially) adapt a syft logger.Logger object to the hclog.Logger interface for syft plugins. +// Note: this does not implement all functionality required by the hclog.Logger. +type LoggerHCLogAdapter struct { + Logger logger.Logger +} + +func NewLoggerHCLogAdapter(logger logger.Logger) *LoggerHCLogAdapter { + return &LoggerHCLogAdapter{ + Logger: logger, + } +} + +// Args are alternating key, val pairs +// keys must be strings +// vals can be any type, but display is implementation specific +// Emit a message and key/value pairs at a provided log level +func (l *LoggerHCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) { + l.Logger.Info(append([]interface{}{msg}, args...)...) +} + +// Emit a message and key/value pairs at the TRACE level +func (l *LoggerHCLogAdapter) Trace(msg string, args ...interface{}) { + l.Logger.Debug(append([]interface{}{msg}, args...)...) +} + +// Emit a message and key/value pairs at the DEBUG level +func (l *LoggerHCLogAdapter) Debug(msg string, args ...interface{}) { + l.Logger.Debug(append([]interface{}{msg}, args...)...) +} + +// Emit a message and key/value pairs at the INFO level +func (l *LoggerHCLogAdapter) Info(msg string, args ...interface{}) { + l.Logger.Info(append([]interface{}{msg}, args...)...) +} + +// Emit a message and key/value pairs at the WARN level +func (l *LoggerHCLogAdapter) Warn(msg string, args ...interface{}) { + l.Logger.Error(append([]interface{}{msg}, args...)...) +} + +// Emit a message and key/value pairs at the ERROR level +func (l *LoggerHCLogAdapter) Error(msg string, args ...interface{}) { + l.Logger.Error(append([]interface{}{msg}, args...)...) +} + +// Indicate if TRACE logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LoggerHCLogAdapter) IsTrace() bool { + // TODO: not implemented + return true +} + +// Indicate if DEBUG logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LoggerHCLogAdapter) IsDebug() bool { + // TODO: not implemented + return true +} + +// Indicate if INFO logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LoggerHCLogAdapter) IsInfo() bool { + // TODO: not implemented + return true +} + +// Indicate if WARN logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LoggerHCLogAdapter) IsWarn() bool { + // TODO: not implemented + return true +} + +// Indicate if ERROR logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LoggerHCLogAdapter) IsError() bool { + // TODO: not implemented + return true +} + +// ImpliedArgs returns With key/value pairs +func (l *LoggerHCLogAdapter) ImpliedArgs() []interface{} { + // TODO: not implemented + return nil +} + +// Creates a sublogger that will always have the given key/value pairs +func (l *LoggerHCLogAdapter) With(args ...interface{}) hclog.Logger { + // TODO: not implemented + return l +} + +// Returns the Name of the logger +func (l *LoggerHCLogAdapter) Name() string { + // TODO: not implemented + return "" +} + +// Create a logger that will prepend the name string on the front of all messages. +// If the logger already has a name, the new value will be appended to the current +// name. That way, a major subsystem can use this to decorate all it's own logs +// without losing context. +func (l *LoggerHCLogAdapter) Named(name string) hclog.Logger { + // TODO: not implemented + return l +} + +// Create a logger that will prepend the name string on the front of all messages. +// This sets the name of the logger to the value directly, unlike Named which honor +// the current name as well. +func (l *LoggerHCLogAdapter) ResetNamed(name string) hclog.Logger { + // TODO: not implemented + return l +} + +// Updates the level. This should affect all sub-loggers as well. If an +// implementation cannot update the level on the fly, it should no-op. +func (l *LoggerHCLogAdapter) SetLevel(level hclog.Level) { + // TODO: not implemented +} + +// Return a value that conforms to the stdlib log.Logger interface +func (l *LoggerHCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { + return log.New(l.StandardWriter(opts), "", log.LstdFlags) +} + +// Return a value that conforms to io.Writer, which can be passed into log.SetOutput() +func (l *LoggerHCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + return &HCLogStdlogAdapter{ + log: l, + inferLevels: opts.InferLevels, + forceLevel: opts.ForceLevel, + } +} diff --git a/internal/logger/logrus.go b/internal/logger/logrus.go index 104d5cef0..0c8188229 100644 --- a/internal/logger/logrus.go +++ b/internal/logger/logrus.go @@ -6,10 +6,15 @@ import ( "io/ioutil" "os" + "github.com/anchore/syft/syft/logger" "github.com/sirupsen/logrus" prefixed "github.com/x-cray/logrus-prefixed-formatter" ) +// integrity check +var _ logger.Logger = &LogrusLogger{} +var _ logger.Logger = &LogrusNestedLogger{} + type LogrusConfig struct { EnableConsole bool EnableFile bool @@ -76,42 +81,50 @@ func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger { } } -func (l *LogrusLogger) Debugf(format string, args ...interface{}) { - l.Logger.Debugf(format, args...) +func (l *LogrusLogger) Errorf(format string, args ...interface{}) { + l.Logger.Errorf(format, args...) } func (l *LogrusLogger) Infof(format string, args ...interface{}) { l.Logger.Infof(format, args...) } -func (l *LogrusLogger) Debug(args ...interface{}) { - l.Logger.Debug(args...) +func (l *LogrusLogger) Debugf(format string, args ...interface{}) { + l.Logger.Debugf(format, args...) +} + +func (l *LogrusLogger) Error(args ...interface{}) { + l.Logger.Error(args...) } func (l *LogrusLogger) Info(args ...interface{}) { l.Logger.Info(args...) } -func (l *LogrusLogger) Errorf(format string, args ...interface{}) { - l.Logger.Errorf(format, args...) +func (l *LogrusLogger) Debug(args ...interface{}) { + l.Logger.Debug(args...) } -func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) { - l.Logger.Debugf(format, args...) +func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) { + l.Logger.Errorf(format, args...) } func (l *LogrusNestedLogger) Infof(format string, args ...interface{}) { l.Logger.Infof(format, args...) } -func (l *LogrusNestedLogger) Debug(args ...interface{}) { - l.Logger.Debug(args...) +func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) { + l.Logger.Debugf(format, args...) +} + +func (l *LogrusNestedLogger) Error(args ...interface{}) { + l.Logger.Error(args...) } func (l *LogrusNestedLogger) Info(args ...interface{}) { l.Logger.Info(args...) } -func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) { - l.Logger.Errorf(format, args...) +func (l *LogrusNestedLogger) Debug(args ...interface{}) { + l.Logger.Debug(args...) } diff --git a/internal/logger/logrus_hclog_adapter.go b/internal/logger/logrus_hclog_adapter.go new file mode 100644 index 000000000..5d4c36b9e --- /dev/null +++ b/internal/logger/logrus_hclog_adapter.go @@ -0,0 +1,181 @@ +package logger + +import ( + "fmt" + "io" + "log" + + "github.com/hashicorp/go-hclog" + "github.com/sirupsen/logrus" +) + +// integrity check +var _ hclog.Logger = &LogrusHCLogAdapter{} + +var levels = map[hclog.Level]logrus.Level{ + hclog.NoLevel: logrus.PanicLevel, + hclog.Error: logrus.ErrorLevel, + hclog.Warn: logrus.WarnLevel, + hclog.Info: logrus.InfoLevel, + hclog.Debug: logrus.DebugLevel, + hclog.Trace: logrus.TraceLevel, +} + +// LogrusHCLogAdapter is used to adapt a logrus logger object to the hclog.Logger interface for syft plugins. +type LogrusHCLogAdapter struct { + logger *logrus.Logger + entry *logrus.Entry +} + +func NewLogrusHCLogAdapter(logger *logrus.Logger, fields map[string]interface{}) *LogrusHCLogAdapter { + if fields != nil { + return &LogrusHCLogAdapter{ + logger: logger, + entry: logger.WithFields(fields), + } + } + return &LogrusHCLogAdapter{ + logger: logger, + } +} + +func (l *LogrusHCLogAdapter) log(level hclog.Level, msg string, args ...interface{}) { + if l.entry != nil { + l.entry.WithFields(makeArgFields(args...)).Log(levels[level], msg) + return + } + l.logger.WithFields(makeArgFields(args...)).Log(levels[level], msg) +} + +// Args are alternating key, val pairs +// keys must be strings +// vals can be any type, but display is implementation specific +// Emit a message and key/value pairs at a provided log level +func (l *LogrusHCLogAdapter) Log(level hclog.Level, msg string, args ...interface{}) { + l.log(level, msg, args...) +} + +// Emit a message and key/value pairs at the TRACE level +func (l *LogrusHCLogAdapter) Trace(msg string, args ...interface{}) { + l.log(hclog.Trace, msg, args...) +} + +// Emit a message and key/value pairs at the DEBUG level +func (l *LogrusHCLogAdapter) Debug(msg string, args ...interface{}) { + l.log(hclog.Debug, msg, args...) +} + +// Emit a message and key/value pairs at the INFO level +func (l *LogrusHCLogAdapter) Info(msg string, args ...interface{}) { + l.log(hclog.Info, msg, args...) +} + +// Emit a message and key/value pairs at the WARN level +func (l *LogrusHCLogAdapter) Warn(msg string, args ...interface{}) { + l.log(hclog.Warn, msg, args...) +} + +// Emit a message and key/value pairs at the ERROR level +func (l *LogrusHCLogAdapter) Error(msg string, args ...interface{}) { + l.log(hclog.Error, msg, args...) +} + +// Indicate if TRACE logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LogrusHCLogAdapter) IsTrace() bool { + return l.logger.Level <= logrus.TraceLevel +} + +// Indicate if DEBUG logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LogrusHCLogAdapter) IsDebug() bool { + return l.logger.Level <= logrus.DebugLevel +} + +// Indicate if INFO logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LogrusHCLogAdapter) IsInfo() bool { + return l.logger.Level <= logrus.InfoLevel +} + +// Indicate if WARN logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LogrusHCLogAdapter) IsWarn() bool { + return l.logger.Level <= logrus.WarnLevel +} + +// Indicate if ERROR logs would be emitted. This and the other Is* guards +// are used to elide expensive logging code based on the current level. +func (l *LogrusHCLogAdapter) IsError() bool { + return l.logger.Level <= logrus.ErrorLevel +} + +// ImpliedArgs returns With key/value pairs +func (l *LogrusHCLogAdapter) ImpliedArgs() []interface{} { + // TODO: not implemented + return nil +} + +// Creates a sublogger that will always have the given key/value pairs +func (l *LogrusHCLogAdapter) With(args ...interface{}) hclog.Logger { + l.entry = l.logger.WithFields(makeArgFields(args...)) + return l +} + +// Returns the Name of the logger +func (l *LogrusHCLogAdapter) Name() string { + // TODO: not implemented + return "" +} + +// Create a logger that will prepend the name string on the front of all messages. +// If the logger already has a name, the new value will be appended to the current +// name. That way, a major subsystem can use this to decorate all it's own logs +// without losing context. +func (l *LogrusHCLogAdapter) Named(name string) hclog.Logger { + return NewLogrusHCLogAdapter(l.logger, logrus.Fields{"name": name}) +} + +// Create a logger that will prepend the name string on the front of all messages. +// This sets the name of the logger to the value directly, unlike Named which honor +// the current name as well. +func (l *LogrusHCLogAdapter) ResetNamed(name string) hclog.Logger { + return NewLogrusHCLogAdapter(l.logger, logrus.Fields{"name": name}) +} + +// Updates the level. This should affect all sub-loggers as well. If an +// implementation cannot update the level on the fly, it should no-op. +func (l *LogrusHCLogAdapter) SetLevel(level hclog.Level) { + l.logger.SetLevel(levels[level]) +} + +// Return a value that conforms to the stdlib log.Logger interface +func (l *LogrusHCLogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { + return log.New(l.StandardWriter(opts), "", log.LstdFlags) +} + +// Return a value that conforms to io.Writer, which can be passed into log.SetOutput() +func (l *LogrusHCLogAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer { + return &HCLogStdlogAdapter{ + log: l, + inferLevels: opts.InferLevels, + forceLevel: opts.ForceLevel, + } +} + +func makeArgFields(args ...interface{}) logrus.Fields { + if len(args)%2 != 0 { + panic(fmt.Errorf("odd number of logger key-value pairs (%d): %+v", len(args), args)) + } + + var key string + var fields = make(logrus.Fields) + for i, arg := range args { + if i%2 == 0 { + key = arg.(string) + } else { + fields[key] = arg + } + } + return fields +} diff --git a/syft/cataloger/catalog.go b/syft/cataloger/catalog.go index 14419732c..e6235f3b5 100644 --- a/syft/cataloger/catalog.go +++ b/syft/cataloger/catalog.go @@ -24,8 +24,9 @@ func Catalog(resolver scope.Resolver, catalogers ...Cataloger) (*pkg.Catalog, er // ask catalogers for files to extract from the image tar for _, a := range catalogers { - fileSelection = append(fileSelection, a.SelectFiles(resolver)...) - log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(fileSelection)) + catalogerSelection := a.SelectFiles(resolver) + fileSelection = append(fileSelection, catalogerSelection...) + log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(catalogerSelection)) filesProcessed.N += int64(len(fileSelection)) } diff --git a/syft/cataloger/cataloger.go b/syft/cataloger/cataloger.go index 10d7fa109..4f859c526 100644 --- a/syft/cataloger/cataloger.go +++ b/syft/cataloger/cataloger.go @@ -8,6 +8,8 @@ package cataloger import ( "io" + "github.com/anchore/syft/syft/cataloger/dpkg" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/cataloger/apkdb" "github.com/anchore/syft/syft/cataloger/bundler" @@ -36,7 +38,7 @@ type Cataloger interface { // All returns a slice of all locally defined catalogers (defined in child packages). func BuiltIn() []Cataloger { return []Cataloger{ - //dpkg.New(), + dpkg.New(), bundler.New(), python.New(), rpmdb.New(), diff --git a/syft/logger/logger.go b/syft/logger/logger.go index bb8f9db06..cfff904b2 100644 --- a/syft/logger/logger.go +++ b/syft/logger/logger.go @@ -5,6 +5,7 @@ package logger type Logger interface { Errorf(format string, args ...interface{}) + Error(args ...interface{}) Infof(format string, args ...interface{}) Info(args ...interface{}) Debugf(format string, args ...interface{}) diff --git a/syft/plugin/config.go b/syft/plugin/config.go index 6969c99f1..3398da4f3 100644 --- a/syft/plugin/config.go +++ b/syft/plugin/config.go @@ -6,5 +6,5 @@ type Config struct { Command string Args []string Env []string - //Sha256 []byte + Sha256 []byte } diff --git a/syft/plugin/grpc/cataloger_client.go b/syft/plugin/grpc/cataloger_client.go index 4aef6fe57..136fdb0fe 100644 --- a/syft/plugin/grpc/cataloger_client.go +++ b/syft/plugin/grpc/cataloger_client.go @@ -3,11 +3,11 @@ package grpc import ( "context" "fmt" - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/cataloger" "io" "io/ioutil" + "github.com/anchore/syft/syft/cataloger" + "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/plugin/proto" @@ -57,8 +57,6 @@ func (c *CatalogerClient) SelectFiles(resolver scope.FileResolver) []file.Refere FileResolverBrokerId: brokerID, }) - log.Debugf("Select Files Response: %+v", resp) - if err != nil { // TODO: nope panic(err) diff --git a/syft/plugin/grpc/file_resolver_client.go b/syft/plugin/grpc/file_resolver_client.go index 48627d6a3..8aaec14d8 100644 --- a/syft/plugin/grpc/file_resolver_client.go +++ b/syft/plugin/grpc/file_resolver_client.go @@ -2,6 +2,7 @@ package grpc import ( "context" + "github.com/anchore/syft/internal/log" "github.com/anchore/stereoscope/pkg/file" diff --git a/syft/plugin/grpc/file_resolver_server.go b/syft/plugin/grpc/file_resolver_server.go index 186ec0c4f..87c7096ec 100644 --- a/syft/plugin/grpc/file_resolver_server.go +++ b/syft/plugin/grpc/file_resolver_server.go @@ -2,7 +2,6 @@ package grpc import ( "context" - "github.com/anchore/syft/internal/log" "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/syft/plugin/proto" @@ -37,15 +36,11 @@ func (m *FileResolverServer) FilesByPath(ctx context.Context, req *proto.FileRes } func (m *FileResolverServer) FilesByGlob(ctx context.Context, req *proto.FileResolverRequest) (resp *proto.FileResolverResponse, err error) { - log.Debugf("FilesByGlob Request: %+v", req) - r, err := m.Impl.FilesByGlob(req.Paths...) if err != nil { return nil, err } - log.Debugf("FilesByGlob Result: %+v", r) - var refs []*proto.FileReference for _, ref := range r { refs = append(refs, &proto.FileReference{ diff --git a/syft/plugin/plugin.go b/syft/plugin/plugin.go index 413ceac2e..56e5aab86 100644 --- a/syft/plugin/plugin.go +++ b/syft/plugin/plugin.go @@ -1,11 +1,13 @@ package plugin import ( + "crypto/sha256" "fmt" - "github.com/hashicorp/go-hclog" - "os" "os/exec" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/internal/logger" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" ) @@ -24,27 +26,33 @@ type Plugin struct { // TODO: type should be in the name like with terraform "terraform--" func NewPlugin(config Config) Plugin { - cmd := exec.Command("sh", "-c", config.Command) //, config.Args...) + //cmd := exec.Command("sh", "-c", config.Command) //, config.Args...) + cmd := exec.Command(config.Command) cmd.Env = append(cmd.Env, config.Env...) - //secureConfig := &plugin.SecureConfig{ - // Checksum: config.Sha256, - // Hash: sha256.New(), - //} + var secureConfig *plugin.SecureConfig + if len(config.Sha256) > 0 { + secureConfig = &plugin.SecureConfig{ + Checksum: config.Sha256, + Hash: sha256.New(), + } + } - // TODO: temp? - logger := hclog.New(&hclog.LoggerOptions{ - Name: config.Name, - Level: hclog.Trace, - Output: os.Stderr, - }) + var pluginLogger hclog.Logger + if logrusLogger, ok := log.Log.(*logger.LogrusLogger); ok { + pluginLogger = logger.NewLogrusHCLogAdapter(logrusLogger.Logger, map[string]interface{}{"plugin": config.Name}) + } else { + // TODO: this does not fully map features, thus logging will be awkward + // TODO: find a better way to map loggers (expand our interface?) + pluginLogger = logger.NewLoggerHCLogAdapter(log.Log) + } clientConfig := plugin.ClientConfig{ HandshakeConfig: config.Type.HandshakeConfig(), VersionedPlugins: versionedPlugins, - //SecureConfig: secureConfig, - Cmd: cmd, - Logger: logger, + SecureConfig: secureConfig, + Cmd: cmd, + Logger: pluginLogger, AllowedProtocols: []plugin.Protocol{ plugin.ProtocolGRPC, }, @@ -56,7 +64,7 @@ func NewPlugin(config Config) Plugin { } } -func (p Plugin) Start() (interface{}, error) { +func (p *Plugin) Start() (interface{}, error) { if p.client != nil { return nil, fmt.Errorf("plugin already started") } @@ -79,7 +87,7 @@ func (p Plugin) Start() (interface{}, error) { return raw, nil } -func (p Plugin) Stop() error { +func (p *Plugin) Stop() error { if p.client == nil { return fmt.Errorf("plugin has not been started") } diff --git a/syft/plugin/repository.go b/syft/plugin/repository.go index 593f22749..dbdfafe69 100644 --- a/syft/plugin/repository.go +++ b/syft/plugin/repository.go @@ -2,8 +2,11 @@ package plugin import ( "fmt" - "github.com/anchore/syft/syft/cataloger" "path/filepath" + "strings" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/cataloger" ) type Repository struct { @@ -35,8 +38,11 @@ func (c *Repository) AddFromDirectory(dir string, pluginTypes ...Type) error { return err } for _, plugin := range plugins { + log.Debugf("added plugin: %q", plugin.Config.Name) c.Add(plugin) } + log.Debugf("%d plugins added", len(plugins)) + return nil } @@ -49,8 +55,9 @@ func (c *Repository) ActivateCatalogers() ([]cataloger.Cataloger, func(), error) var plugins []Plugin var deactivateFn = func() { for _, plugin := range plugins { - // TODO: handle error by log - plugin.Stop() + if err := plugin.Stop(); err != nil { + log.Errorf("failed to stop plugin: %w", err) + } } } @@ -88,10 +95,10 @@ func Discover(dir string, pluginTypes ...Type) ([]Plugin, error) { var plugins []Plugin for _, pluginType := range pluginTypes { - // look into a sub dir named by the plugin type - searchDir := filepath.Join(dir, pluginType.String()) + pluginPrefix := pluginType.String() + pluginGlob := fmt.Sprintf("%s-*", pluginPrefix) + paths, err := filepath.Glob(filepath.Join(dir, pluginGlob)) - paths, err := filepath.Glob(filepath.Join(searchDir, "*")) if err != nil { return nil, err } @@ -100,7 +107,7 @@ func Discover(dir string, pluginTypes ...Type) ([]Plugin, error) { // TODO: should we use a config for some of this? plugins = append(plugins, NewPlugin(Config{ // TODO: should the name be org/name instead of just name? this implies changing the dir storage too - Name: filepath.Base(path), + Name: strings.TrimPrefix(filepath.Base(path), pluginPrefix+"-"), Type: pluginType, Command: path, Args: nil, // TODO diff --git a/syft/plugin/type.go b/syft/plugin/type.go index 6164d3f8e..108caaf2b 100644 --- a/syft/plugin/type.go +++ b/syft/plugin/type.go @@ -31,7 +31,7 @@ func (p Type) HandshakeConfig() plugin.HandshakeConfig { case TypeCataloger: return plugin.HandshakeConfig{ ProtocolVersion: 1, - MagicCookieKey: "SYFT_CATALOGER_PLUGIN", + MagicCookieKey: "SYFT_PLUGIN_MAGIC_COOKIE", MagicCookieValue: "0f86cc7f-6f97-410e-a844-087cd12e36e3", } default: