From 11731fac402c871006e5d5cd68374529f402586e Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Sat, 1 Aug 2020 11:58:10 -0400 Subject: [PATCH] replace zap logger with logrus (#80) --- cmd/cmd.go | 23 ++- cmd/db_update.go | 18 +-- cmd/root.go | 3 - go.mod | 5 +- go.sum | 16 +- grype/db/curator.go | 73 +++++---- grype/event/parsers/parsers.go | 13 +- grype/lib.go | 17 +-- grype/presenter/table/presenter.go | 2 +- .../snapshot/TestEmptyTablePresenter.golden | 2 +- internal/config/config.go | 20 +-- internal/logger/logrus.go | 115 ++++++++++++++ internal/logger/zap.go | 141 ------------------ internal/ui/etui/ephemeral_tui.go | 15 ++ ui/event_handlers.go | 77 +++++----- ui/handler.go | 5 +- 16 files changed, 274 insertions(+), 271 deletions(-) create mode 100644 internal/logger/logrus.go delete mode 100644 internal/logger/zap.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 233a8cb9..4e2ad8e3 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,22 +4,21 @@ import ( "fmt" "os" - "github.com/anchore/stereoscope" - "github.com/wagoodman/go-partybus" - "github.com/anchore/grype/grype" "github.com/anchore/grype/internal/config" "github.com/anchore/grype/internal/format" "github.com/anchore/grype/internal/logger" + "github.com/anchore/stereoscope" "github.com/anchore/syft/syft" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" - "go.uber.org/zap" + "github.com/wagoodman/go-partybus" "gopkg.in/yaml.v2" ) var appConfig *config.Application -var log *zap.SugaredLogger +var log *logrus.Logger var cliOnlyOpts config.CliOnlyOptions var eventBus *partybus.Bus var eventSubscription *partybus.Subscription @@ -70,7 +69,7 @@ func initAppConfig() { } func initLogging() { - cfg := logger.LogConfig{ + cfg := logger.LogrusConfig{ EnableConsole: (appConfig.Log.FileLocation == "" || appConfig.CliOptions.Verbosity > 0) && !appConfig.Quiet, EnableFile: appConfig.Log.FileLocation != "", Level: appConfig.Log.LevelOpt, @@ -78,10 +77,18 @@ func initLogging() { FileLocation: appConfig.Log.FileLocation, } - logWrapper := logger.NewZapLogger(cfg) + logWrapper := logger.NewLogrusLogger(cfg) + log = logWrapper.Logger grype.SetLogger(logWrapper) - syft.SetLogger(logWrapper) + + // add a structured field to all loggers of dependencies + syft.SetLogger(&logger.LogrusNestedLogger{ + Logger: log.WithField("from-lib", "syft"), + }) + stereoscope.SetLogger(&logger.LogrusNestedLogger{ + Logger: log.WithField("from-lib", "steroscope"), + }) } func logAppConfig() { diff --git a/cmd/db_update.go b/cmd/db_update.go index 12f4e8f3..ba2935fc 100644 --- a/cmd/db_update.go +++ b/cmd/db_update.go @@ -27,23 +27,17 @@ func init() { func runDbUpdateCmd(_ *cobra.Command, _ []string) int { dbCurator := db.NewCurator(appConfig.Db.ToCuratorConfig()) - updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable() + updated, err := dbCurator.Update() if err != nil { - // TODO: should this be so fatal? we can certainly continue with a warning... - log.Errorf("unable to check for vulnerability database update: %+v", err) + log.Errorf("unable to update vulnerability database: %+v", err) return 1 } - if updateAvailable { - err = dbCurator.UpdateTo(updateEntry) - if err != nil { - log.Errorf("unable to update vulnerability database: %+v", err) - return 1 - } - } else { - fmt.Println("No vulnerability database update available") + + if updated { + fmt.Println("Vulnerability database updated!") return 0 } - fmt.Println("Vulnerability database updated!") + fmt.Println("No vulnerability database update available") return 0 } diff --git a/cmd/root.go b/cmd/root.go index a14d2e97..451938c8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -129,9 +129,6 @@ func startWorker(userInput string) <-chan error { go func() { defer wg.Done() - // TODO: move this log entry to syft - log.Info("Cataloging image") - catalog, _, theDistro, err = syft.Catalog(userInput, appConfig.ScopeOpt) if err != nil { errs <- fmt.Errorf("failed to catalog: %w", err) diff --git a/go.mod b/go.mod index 1f24448d..a6e739ff 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/grype-db v0.0.0-20200730184339-fc1f236ce8b2 github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639 - github.com/anchore/syft v0.1.0-beta.2.0.20200730191658-271ba35c8520 + github.com/anchore/syft v0.1.0-beta.2.0.20200801155638-78515da28521 github.com/dustin/go-humanize v1.0.0 github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6 github.com/go-test/deep v1.0.7 @@ -19,13 +19,14 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/olekukonko/tablewriter v0.0.4 github.com/sergi/go-diff v1.1.0 + github.com/sirupsen/logrus v1.6.0 github.com/spf13/afero v1.3.2 github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.7.0 github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240 github.com/wagoodman/jotframe v0.0.0-20200730190914-3517092dd163 - go.uber.org/zap v1.15.0 + github.com/x-cray/logrus-prefixed-formatter v0.5.2 golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 50bf6d9b..24746e01 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e h1:QBwtrM0MXi0 github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g= github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639 h1:J1oytkj+aBuACNF2whtEiVxRXIZ8zwT+EiPTqm/FvwA= github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639/go.mod h1:WntReQTI/I27FOQ87UgLVVzWgku6+ZsqfOTLxpIZFCs= -github.com/anchore/syft v0.1.0-beta.2.0.20200730191658-271ba35c8520 h1:PxH4oUP56qz0afZO2YGzHRCftLrYEVHUExqukmhTlLI= -github.com/anchore/syft v0.1.0-beta.2.0.20200730191658-271ba35c8520/go.mod h1:usYLBr5UXjUSVgki8Zy/wI7JC+5t5izMF4zaEEczr3g= +github.com/anchore/syft v0.1.0-beta.2.0.20200801155638-78515da28521 h1:a3s2ZqdtZIF5Hplk2otwSAzpuvKfVQsyTQBhXNFmK5A= +github.com/anchore/syft v0.1.0-beta.2.0.20200801155638-78515da28521/go.mod h1:0EPCQUJaV1ZmuCuxJl0jEJJs3QNx+VA0yGo6/BzetfQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -461,6 +461,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -543,6 +544,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= @@ -553,6 +555,7 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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.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= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -566,6 +569,7 @@ github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb44 github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -607,12 +611,14 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -777,14 +783,14 @@ github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59b github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg= github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw= github.com/wagoodman/go-progress v0.0.0-20200621122631-1a2120f0695a/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= -github.com/wagoodman/go-progress v0.0.0-20200621153512-2778c704bf22 h1:GYaiTP0ywrCjJ4qMxxCg+YKPSDMeFJg6i1X9X55LJCA= -github.com/wagoodman/go-progress v0.0.0-20200621153512-2778c704bf22/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240 h1:r6BlIP7CVZtMlxUQhT40h1IE1TzEgKVqwmsVGuscvdk= github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= github.com/wagoodman/go-rpmdb v0.0.0-20200719223757-ce54a4b0607b h1:elYGLFZPymeTWJ6qA3tIzFet3LQ9D/Jl6HLWNyFjdQc= github.com/wagoodman/go-rpmdb v0.0.0-20200719223757-ce54a4b0607b/go.mod h1:MjoIZzKmbYfcpbC6ARWMcHijAjtLBViDaHcayXKWQWI= github.com/wagoodman/jotframe v0.0.0-20200730190914-3517092dd163 h1:qoZwR+bHbFFNirY4Yt7lqbOXnFAMnlFfR89w0TXwjrc= github.com/wagoodman/jotframe v0.0.0-20200730190914-3517092dd163/go.mod h1:DzXZ1wfRedNhC3KQTick8Gf3CEPMFHsP5k4R/ldjKtw= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= @@ -1162,6 +1168,7 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -1173,6 +1180,7 @@ gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/grype/db/curator.go b/grype/db/curator.go index 1ba15d2b..8e1065ba 100644 --- a/grype/db/curator.go +++ b/grype/db/curator.go @@ -83,6 +83,52 @@ func (c *Curator) Delete() error { return c.fs.RemoveAll(c.config.DbDir) } +func (c *Curator) Update() (bool, error) { + // let consumers know of a monitorable event (download + import stages) + importProgress := &progress.Manual{ + Total: 1, + } + stage := &progress.Stage{ + Current: "checking for update", + } + downloadProgress := &progress.Manual{ + Total: 1, + } + aggregateProgress := progress.NewAggregator(progress.DefaultStrategy, downloadProgress, importProgress) + + bus.Publish(partybus.Event{ + Type: event.UpdateVulnerabilityDatabase, + Value: progress.StagedProgressable(&struct { + progress.Stager + progress.Progressable + }{ + Stager: progress.Stager(stage), + Progressable: progress.Progressable(aggregateProgress), + }), + }) + + defer downloadProgress.SetCompleted() + defer importProgress.SetCompleted() + + updateAvailable, updateEntry, err := c.IsUpdateAvailable() + if err != nil { + // we want to continue if possible even if we can't check for an update + log.Infof("unable to check for vulnerability database update") + log.Debugf("check for vulnerability update failed: %+v", err) + } + if updateAvailable { + log.Infof("Downloading new vulnerability DB") + err = c.UpdateTo(updateEntry, downloadProgress, importProgress, stage) + if err != nil { + return false, fmt.Errorf("unable to update vulnerability database: %w", err) + } + log.Infof("Updated vulnerability DB to version=%d built=%q", updateEntry.Version, updateEntry.Built.String()) + return true, nil + } + stage.Current = "no update available" + return false, nil +} + func (c *Curator) IsUpdateAvailable() (bool, *curation.ListingEntry, error) { log.Debugf("checking for available database updates") @@ -153,31 +199,8 @@ func (c *Curator) ImportFrom(dbArchivePath string) error { return c.fs.RemoveAll(tempDir) } -func (c *Curator) UpdateTo(listing *curation.ListingEntry) error { - // let consumers know of a monitorable event (download + import stages) - importProgress := &progress.Manual{ - Total: 1, - } - stage := &progress.Stage{ - Current: "downloading", - } - downloadProgress := &progress.Manual{ - Total: 1, - } - aggregateProgress := progress.NewAggregator(progress.DefaultStrategy, downloadProgress, importProgress) - - bus.Publish(partybus.Event{ - Type: event.UpdateVulnerabilityDatabase, - Source: path.Base(listing.URL.Path), - Value: progress.StagedProgressable(&struct { - progress.Stager - progress.Progressable - }{ - Stager: progress.Stager(stage), - Progressable: progress.Progressable(aggregateProgress), - }), - }) - +func (c *Curator) UpdateTo(listing *curation.ListingEntry, downloadProgress, importProgress *progress.Manual, stage *progress.Stage) error { + stage.Current = "downloading" // note: the temp directory is persisted upon download/validation/activation failure to allow for investigation tempDir, err := c.download(listing, downloadProgress) if err != nil { diff --git a/grype/event/parsers/parsers.go b/grype/event/parsers/parsers.go index e8da980f..804d2221 100644 --- a/grype/event/parsers/parsers.go +++ b/grype/event/parsers/parsers.go @@ -49,22 +49,17 @@ func ParseAppUpdateAvailable(e partybus.Event) (string, error) { return newVersion, nil } -func ParseUpdateVulnerabilityDatabase(e partybus.Event) (string, progress.StagedProgressable, error) { +func ParseUpdateVulnerabilityDatabase(e partybus.Event) (progress.StagedProgressable, error) { if err := checkEventType(e.Type, event.UpdateVulnerabilityDatabase); err != nil { - return "", nil, err - } - - dbArchiveName, ok := e.Source.(string) - if !ok { - return "", nil, newPayloadErr(e.Type, "Source", e.Source) + return nil, err } prog, ok := e.Value.(progress.StagedProgressable) if !ok { - return "", nil, newPayloadErr(e.Type, "Value", e.Value) + return nil, newPayloadErr(e.Type, "Value", e.Value) } - return dbArchiveName, prog, nil + return prog, nil } func ParseVulnerabilityScanningStarted(e partybus.Event) (*matcher.Monitor, error) { diff --git a/grype/lib.go b/grype/lib.go index b17f7df9..6a7de2ff 100644 --- a/grype/lib.go +++ b/grype/lib.go @@ -1,8 +1,6 @@ package grype import ( - "fmt" - "github.com/anchore/grype/internal/bus" "github.com/wagoodman/go-partybus" @@ -21,7 +19,6 @@ import ( ) func FindVulnerabilities(provider vulnerability.Provider, userImageStr string, scopeOpt scope.Option) (result.Result, *pkg.Catalog, *scope.Scope, error) { - log.Info("Cataloging image") catalog, theScope, theDistro, err := syft.Catalog(userImageStr, scopeOpt) if err != nil { return result.Result{}, nil, nil, err @@ -46,19 +43,9 @@ func LoadVulnerabilityDb(cfg db.Config, update bool) (vulnerability.Provider, er dbCurator := db.NewCurator(cfg) if update { - updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable() + _, err := dbCurator.Update() if err != nil { - // we want to continue if possible even if we can't check for an update - log.Infof("unable to check for vulnerability database update") - log.Debugf("check for vulnerability update failed: %+v", err) - } - if updateAvailable { - log.Infof("Downloading new vulnerability DB") - err = dbCurator.UpdateTo(updateEntry) - if err != nil { - return nil, fmt.Errorf("unable to update vulnerability database: %w", err) - } - log.Infof("Updated vulnerability DB to version=%d built=%q", updateEntry.Version, updateEntry.Built.String()) + return nil, err } } diff --git a/grype/presenter/table/presenter.go b/grype/presenter/table/presenter.go index 07028b0d..22f2b423 100644 --- a/grype/presenter/table/presenter.go +++ b/grype/presenter/table/presenter.go @@ -39,7 +39,7 @@ func (pres *Presenter) Present(output io.Writer) error { } if len(rows) == 0 { - _, err := io.WriteString(output, "No vulnerabilities found") + _, err := io.WriteString(output, "No vulnerabilities found\n") return err } diff --git a/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden b/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden index 6079c3ff..8900c02c 100644 --- a/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden +++ b/grype/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden @@ -1 +1 @@ -No vulnerabilities found \ No newline at end of file +No vulnerabilities found diff --git a/internal/config/config.go b/internal/config/config.go index e132c718..f906bfd4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,8 @@ import ( "path" "strings" + "github.com/sirupsen/logrus" + "github.com/anchore/grype/grype/presenter" "github.com/adrg/xdg" @@ -13,7 +15,6 @@ import ( "github.com/anchore/syft/syft/scope" "github.com/mitchellh/go-homedir" "github.com/spf13/viper" - "go.uber.org/zap/zapcore" ) type CliOnlyOptions struct { @@ -37,7 +38,7 @@ type Application struct { type Logging struct { Structured bool `mapstructure:"structured"` - LevelOpt zapcore.Level + LevelOpt logrus.Level Level string `mapstructure:"level"` FileLocation string `mapstructure:"file"` } @@ -116,27 +117,28 @@ func (cfg *Application) Build() error { // TODO: this is bad: quiet option trumps all other logging options // we should be able to quiet the console logging and leave file logging alone... // ... this will be an enhancement for later - cfg.Log.LevelOpt = zapcore.PanicLevel + cfg.Log.LevelOpt = logrus.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) + lvl, err := logrus.ParseLevel(strings.ToLower(cfg.Log.Level)) if err != nil { - return fmt.Errorf("bad log level value '%s': %+v", cfg.Log.Level, err) + return fmt.Errorf("bad log level configured (%q): %w", cfg.Log.Level, err) } + // set the log level explicitly + cfg.Log.LevelOpt = lvl } else { // set the log level implicitly switch v := cfg.CliOptions.Verbosity; { case v == 1: - cfg.Log.LevelOpt = zapcore.InfoLevel + cfg.Log.LevelOpt = logrus.InfoLevel case v >= 2: - cfg.Log.LevelOpt = zapcore.DebugLevel + cfg.Log.LevelOpt = logrus.DebugLevel default: - cfg.Log.LevelOpt = zapcore.ErrorLevel + cfg.Log.LevelOpt = logrus.ErrorLevel } } } diff --git a/internal/logger/logrus.go b/internal/logger/logrus.go new file mode 100644 index 00000000..bfe105a3 --- /dev/null +++ b/internal/logger/logrus.go @@ -0,0 +1,115 @@ +package logger + +import ( + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/sirupsen/logrus" + prefixed "github.com/x-cray/logrus-prefixed-formatter" +) + +type LogrusConfig struct { + EnableConsole bool + EnableFile bool + Structured bool + Level logrus.Level + FileLocation string +} + +type LogrusLogger struct { + Config LogrusConfig + Logger *logrus.Logger +} + +type LogrusNestedLogger struct { + Logger *logrus.Entry +} + +func NewLogrusLogger(cfg LogrusConfig) *LogrusLogger { + appLogger := logrus.New() + + var output io.Writer + switch { + case cfg.EnableConsole && cfg.EnableFile: + logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, 0755) + if err != nil { + panic(fmt.Errorf("unable to setup log file: %w", err)) + } + output = io.MultiWriter(os.Stderr, logFile) + case cfg.EnableConsole: + output = os.Stderr + case cfg.EnableFile: + logFile, err := os.OpenFile(cfg.FileLocation, os.O_WRONLY|os.O_CREATE, 0755) + if err != nil { + panic(fmt.Errorf("unable to setup log file: %w", err)) + } + output = logFile + default: + output = ioutil.Discard + } + + appLogger.SetOutput(output) + appLogger.SetLevel(cfg.Level) + + if cfg.Structured { + appLogger.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: "2006-01-02 15:04:05", + DisableTimestamp: false, + DisableHTMLEscape: false, + PrettyPrint: false, + }) + } else { + appLogger.SetFormatter(&prefixed.TextFormatter{ + TimestampFormat: "2006-01-02 15:04:05", + ForceColors: true, + ForceFormatting: true, + }) + } + + return &LogrusLogger{ + Config: cfg, + Logger: appLogger, + } +} + +func (l *LogrusLogger) Debugf(format string, args ...interface{}) { + l.Logger.Debugf(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) Info(args ...interface{}) { + l.Logger.Info(args...) +} + +func (l *LogrusLogger) Errorf(format string, args ...interface{}) { + l.Logger.Errorf(format, args...) +} + +func (l *LogrusNestedLogger) Debugf(format string, args ...interface{}) { + l.Logger.Debugf(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) Info(args ...interface{}) { + l.Logger.Info(args...) +} + +func (l *LogrusNestedLogger) Errorf(format string, args ...interface{}) { + l.Logger.Errorf(format, args...) +} diff --git a/internal/logger/zap.go b/internal/logger/zap.go deleted file mode 100644 index c68c1c73..00000000 --- a/internal/logger/zap.go +++ /dev/null @@ -1,141 +0,0 @@ -package logger - -import ( - "os" - - "github.com/anchore/grype/internal/format" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -var levelToColor = map[zapcore.Level]format.Color{ - zapcore.DebugLevel: format.Magenta, - zapcore.InfoLevel: format.Blue, - zapcore.WarnLevel: format.Yellow, - zapcore.ErrorLevel: format.Red, - zapcore.DPanicLevel: format.Red, - zapcore.PanicLevel: format.Red, - zapcore.FatalLevel: format.Red, -} - -type LogConfig struct { - EnableConsole bool - EnableFile bool - Structured bool - Level zapcore.Level - FileLocation string -} - -type ZapLogger struct { - Config LogConfig - Logger *zap.SugaredLogger -} - -// TODO: Consider a human readable text encoder for better field handeling: -// - https://github.com/uber-go/zap/issues/570 -// - https://github.com/uber-go/zap/pull/123 -// - TextEncoder w/ old interface: https://github.com/uber-go/zap/blob/6c2107996402d47d559199b78e1c44747fe732f9/text_encoder.go -// - New interface example: https://github.com/uber-go/zap/blob/c2633d6de2d6e1170ad8f150660e3cf5310067c8/zapcore/json_encoder.go -// - Register the encoder: https://github.com/uber-go/zap/blob/v1.15.0/encoder.go -func NewZapLogger(config LogConfig) *ZapLogger { - appLogger := ZapLogger{ - Config: config, - } - 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(appLogger.getConsoleEncoder(config), writer, config.Level) - cores = append(cores, core) - } - - if config.EnableFile { - writer := zapcore.AddSync(appLogger.logFileWriter(config.FileLocation)) - core := zapcore.NewCore(appLogger.fileEncoder(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) - appLogger.Logger = zap.New( - combinedCore, - zap.AddCallerSkip(2), - zap.AddCaller(), - ).Sugar() - - return &appLogger -} - -func (l *ZapLogger) GetNamedLogger(name string) *ZapLogger { - return &ZapLogger{ - Logger: l.Logger.Named(name), - } -} - -func (l *ZapLogger) getConsoleEncoder(config LogConfig) zapcore.Encoder { - encoderConfig := zap.NewProductionEncoderConfig() - if config.Structured { - encoderConfig.EncodeName = zapcore.FullNameEncoder - encoderConfig.EncodeCaller = zapcore.FullCallerEncoder - return zapcore.NewJSONEncoder(encoderConfig) - } - encoderConfig.EncodeTime = nil - encoderConfig.EncodeCaller = nil - encoderConfig.EncodeLevel = l.consoleLevelEncoder - encoderConfig.EncodeName = l.nameEncoder - return zapcore.NewConsoleEncoder(encoderConfig) -} - -func (l *ZapLogger) nameEncoder(loggerName string, enc zapcore.PrimitiveArrayEncoder) { - enc.AppendString("[" + loggerName + "]") -} - -func (l *ZapLogger) consoleLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { - if level != zapcore.InfoLevel || l.Config.Level == zapcore.DebugLevel { - color, ok := levelToColor[level] - if !ok { - enc.AppendString("[" + level.CapitalString() + "]") - } else { - enc.AppendString("[" + color.Format(level.CapitalString()) + "]") - } - } -} - -func (l *ZapLogger) fileEncoder(config LogConfig) zapcore.Encoder { - encoderConfig := zap.NewProductionEncoderConfig() - encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder - encoderConfig.EncodeName = zapcore.FullNameEncoder - encoderConfig.EncodeCaller = zapcore.FullCallerEncoder - if config.Structured { - return zapcore.NewJSONEncoder(encoderConfig) - } - return zapcore.NewConsoleEncoder(encoderConfig) -} - -func (l *ZapLogger) logFileWriter(location string) zapcore.WriteSyncer { - file, _ := os.Create(location) - return zapcore.AddSync(file) -} - -func (l *ZapLogger) Debugf(format string, args ...interface{}) { - l.Logger.Debugf(format, args...) -} - -func (l *ZapLogger) Infof(format string, args ...interface{}) { - l.Logger.Infof(format, args...) -} - -func (l *ZapLogger) Debug(args ...interface{}) { - l.Logger.Debug(args...) -} - -func (l *ZapLogger) Info(args ...interface{}) { - l.Logger.Info(args...) -} - -func (l *ZapLogger) Errorf(format string, args ...interface{}) { - l.Logger.Errorf(format, args...) -} diff --git a/internal/ui/etui/ephemeral_tui.go b/internal/ui/etui/ephemeral_tui.go index ab6a9b5c..9f4854f0 100644 --- a/internal/ui/etui/ephemeral_tui.go +++ b/internal/ui/etui/ephemeral_tui.go @@ -1,6 +1,7 @@ package etui import ( + "bytes" "context" "fmt" "os" @@ -8,6 +9,7 @@ import ( grypeEvent "github.com/anchore/grype/grype/event" "github.com/anchore/grype/internal/log" + "github.com/anchore/grype/internal/logger" "github.com/anchore/grype/internal/ui/common" grypeUI "github.com/anchore/grype/ui" "github.com/wagoodman/go-partybus" @@ -35,6 +37,13 @@ func setupScreen(output *os.File) *frame.Frame { func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscription) error { output := os.Stderr + // prep the logger to not clobber the screen from now on (logrus only) + logBuffer := bytes.NewBufferString("") + logWrapper, ok := log.Log.(*logger.LogrusLogger) + if ok { + logWrapper.Logger.SetOutput(logBuffer) + } + // hide cursor _, _ = fmt.Fprint(output, "\x1b[?25l") // show cursor @@ -49,6 +58,8 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr if !isClosed { fr.Close() frame.Close() + // flush any errors to the screen before the report + fmt.Fprint(output, logBuffer.String()) } }() @@ -85,9 +96,13 @@ eventLoop: // finish before discontinuing dynamic content and showing the final report wg.Wait() fr.Close() + // TODO: there is a race condition within frame.Close() that sometimes leads to an extra blank line being output frame.Close() isClosed = true + // flush any errors to the screen before the report + fmt.Fprint(output, logBuffer.String()) + if err := common.VulnerabilityScanningFinishedHandler(e); err != nil { log.Errorf("unable to show %s event: %+v", e.Type, err) } diff --git a/ui/event_handlers.go b/ui/event_handlers.go index 2a598dc5..048500f1 100644 --- a/ui/event_handlers.go +++ b/ui/event_handlers.go @@ -37,8 +37,8 @@ func startProcess() (format.Simple, *common.Spinner) { return formatter, &spinner } -func DownloadingVulnerabilityDatabaseHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { - _, prog, err := grypeEventParsers.ParseUpdateVulnerabilityDatabase(event) +func (r *Handler) UpdateVulnerabilityDatabaseHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + prog, err := grypeEventParsers.ParseUpdateVulnerabilityDatabase(event) if err != nil { return fmt.Errorf("bad FetchImage event: %w", err) } @@ -50,45 +50,46 @@ func DownloadingVulnerabilityDatabaseHandler(ctx context.Context, fr *frame.Fram wg.Add(1) - go func() { - defer line.Close() - defer wg.Done() - formatter, spinner := startProcess() - stream := progress.Stream(ctx, prog, 150*time.Millisecond) - title := tileFormat.Sprint("Updating Vulnerability DB...") + formatter, spinner := startProcess() + stream := progress.Stream(ctx, prog, 150*time.Millisecond) + title := tileFormat.Sprint("Vulnerability DB") - formatFn := func(p progress.Progress) { - progStr, err := formatter.Format(p) - spin := color.Magenta.Sprint(spinner.Next()) - if err != nil { - _, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err)) - } else { - var auxInfo string - switch prog.Stage() { - case "downloading": - auxInfo = auxInfoFormat.Sprintf("[%s / %s]", humanize.Bytes(uint64(prog.Current())), humanize.Bytes(uint64(prog.Size()))) - default: - auxInfo = auxInfoFormat.Sprintf("[%s]", prog.Stage()) - } - - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s %s", spin, title, progStr, auxInfo)) + formatFn := func(p progress.Progress) { + progStr, err := formatter.Format(p) + spin := color.Magenta.Sprint(spinner.Next()) + if err != nil { + _, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err)) + } else { + var auxInfo string + switch prog.Stage() { + case "downloading": + auxInfo = auxInfoFormat.Sprintf("[%s / %s]", humanize.Bytes(uint64(prog.Current())), humanize.Bytes(uint64(prog.Size()))) + default: + progStr = "" + auxInfo = auxInfoFormat.Sprintf("[%s]", prog.Stage()) } + + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s %s", spin, title, progStr, auxInfo)) } + } + + go func() { + defer wg.Done() formatFn(progress.Progress{}) - for p := range stream { formatFn(p) } spin := color.Green.Sprint(completedStatus) - title = tileFormat.Sprint("Updated Vulnerability DB") - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + title = tileFormat.Sprint("Vulnerability DB") + auxInfo := auxInfoFormat.Sprintf("[%s]", prog.Stage()) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+" %s", spin, title, auxInfo)) }() return err } -func VulnerabilityScanningStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { +func (r *Handler) VulnerabilityScanningStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { monitor, err := grypeEventParsers.ParseVulnerabilityScanningStarted(event) if err != nil { return fmt.Errorf("bad %s event: %w", event.Type, err) @@ -101,18 +102,18 @@ func VulnerabilityScanningStartedHandler(ctx context.Context, fr *frame.Frame, e wg.Add(1) - go func() { - defer line.Close() - defer wg.Done() - _, spinner := startProcess() - stream := progress.StreamMonitors(ctx, []progress.Monitorable{monitor.PackagesProcessed, monitor.VulnerabilitiesDiscovered}, 50*time.Millisecond) - title := tileFormat.Sprint("Scanning image...") + _, spinner := startProcess() + stream := progress.StreamMonitors(ctx, []progress.Monitorable{monitor.PackagesProcessed, monitor.VulnerabilitiesDiscovered}, 50*time.Millisecond) + title := tileFormat.Sprint("Scanning image...") - formatFn := func(val int64) { - spin := color.Magenta.Sprint(spinner.Next()) - auxInfo := auxInfoFormat.Sprintf("[vulnerabilities %d]", val) - _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo)) - } + formatFn := func(val int64) { + spin := color.Magenta.Sprint(spinner.Next()) + auxInfo := auxInfoFormat.Sprintf("[vulnerabilities %d]", val) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo)) + } + + go func() { + defer wg.Done() formatFn(0) for p := range stream { diff --git a/ui/handler.go b/ui/handler.go index 26c9f782..d8ab220b 100644 --- a/ui/handler.go +++ b/ui/handler.go @@ -32,10 +32,9 @@ func (r *Handler) RespondsTo(event partybus.Event) bool { func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { switch event.Type { case grypeEvent.VulnerabilityScanningStarted: - return VulnerabilityScanningStartedHandler(ctx, fr, event, wg) - + return r.VulnerabilityScanningStartedHandler(ctx, fr, event, wg) case grypeEvent.UpdateVulnerabilityDatabase: - return DownloadingVulnerabilityDatabaseHandler(ctx, fr, event, wg) + return r.UpdateVulnerabilityDatabaseHandler(ctx, fr, event, wg) default: return r.syftHandler.Handle(ctx, fr, event, wg) }