From 926b5f2a50de387289f93a7524c7b45e21b0beff Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 25 Jun 2020 10:39:11 -0400 Subject: [PATCH] add and use event bus for UI progress --- .golangci.yaml | 2 + cmd/cmd.go | 14 +++ cmd/init.go | 15 ++- cmd/root.go | 92 ++++++++------- go.mod | 15 ++- go.sum | 67 ++++++++--- imgbom/cataloger/controller.go | 30 +++++ imgbom/event/event.go | 8 ++ imgbom/event/parsers/parsers.go | 61 ++++++++++ imgbom/lib.go | 6 + imgbom/presenter/json/presenter.go | 32 +++--- imgbom/presenter/json/presenter_test.go | 5 +- imgbom/presenter/presenter.go | 9 +- imgbom/presenter/text/presenter.go | 22 ++-- imgbom/presenter/text/presenter_test.go | 4 +- internal/bus/bus.go | 19 ++++ internal/logger/zap.go | 2 +- internal/ui/common/event_handlers.go | 22 ++++ internal/ui/etui/ephemeral_tui.go | 102 +++++++++++++++++ internal/ui/etui/event_handlers.go | 145 ++++++++++++++++++++++++ internal/ui/etui/spinner.go | 72 ++++++++++++ internal/ui/logger_output.go | 42 +++++++ internal/ui/select.go | 28 +++++ internal/ui/ui.go | 7 ++ 24 files changed, 725 insertions(+), 96 deletions(-) create mode 100644 cmd/cmd.go create mode 100644 imgbom/event/event.go create mode 100644 imgbom/event/parsers/parsers.go create mode 100644 internal/bus/bus.go create mode 100644 internal/ui/common/event_handlers.go create mode 100644 internal/ui/etui/ephemeral_tui.go create mode 100644 internal/ui/etui/event_handlers.go create mode 100644 internal/ui/etui/spinner.go create mode 100644 internal/ui/logger_output.go create mode 100644 internal/ui/select.go create mode 100644 internal/ui/ui.go diff --git a/.golangci.yaml b/.golangci.yaml index f42162be8..7f8a05a80 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,6 +1,8 @@ linters-settings: funlen: lines: 70 + gocognit: + min-complexity: 35 linters: # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 000000000..cf3eea3b6 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "os" + + "github.com/anchore/imgbom/internal/log" +) + +func Execute() { + if err := rootCmd.Execute(); err != nil { + log.Errorf("could not start application: %w", err) + os.Exit(1) + } +} diff --git a/cmd/init.go b/cmd/init.go index d81588bf1..d532747a8 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -7,15 +7,17 @@ import ( "github.com/anchore/imgbom/imgbom" "github.com/anchore/imgbom/internal/config" "github.com/anchore/imgbom/internal/format" + "github.com/anchore/imgbom/internal/log" "github.com/anchore/imgbom/internal/logger" "github.com/anchore/stereoscope" "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 eventBus *partybus.Bus +var eventSubscription *partybus.Subscription func initAppConfig() { cfg, err := config.LoadConfigFromFile(viper.GetViper(), &cliOpts) @@ -36,7 +38,6 @@ func initLogging() { } logWrapper := logger.NewZapLogger(config) - log = logWrapper.Logger imgbom.SetLogger(logWrapper) stereoscope.SetLogger(logWrapper) } @@ -50,3 +51,11 @@ func logAppConfig() { log.Debugf("Application config:\n%+v", format.Magenta.Format(string(appCfgStr))) } } + +func initEventBus() { + eventBus = partybus.NewBus() + eventSubscription = eventBus.Subscribe() + + stereoscope.SetBus(eventBus) + imgbom.SetBus(eventBus) +} diff --git a/cmd/root.go b/cmd/root.go index dc80eb548..94534dbe6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,10 +5,15 @@ import ( "os" "github.com/anchore/imgbom/imgbom" + "github.com/anchore/imgbom/imgbom/event" "github.com/anchore/imgbom/imgbom/presenter" "github.com/anchore/imgbom/internal" + "github.com/anchore/imgbom/internal/bus" + "github.com/anchore/imgbom/internal/log" + "github.com/anchore/imgbom/internal/ui" "github.com/anchore/stereoscope" "github.com/spf13/cobra" + "github.com/wagoodman/go-partybus" ) var rootCmd = &cobra.Command{ @@ -23,59 +28,62 @@ Supports the following image sources: "appName": internal.ApplicationName, }), Args: cobra.MaximumNArgs(1), - Run: runCmdWrapper, + Run: func(cmd *cobra.Command, args []string) { + os.Exit(doRunCmd(cmd, args)) + }, } func init() { setCliOptions() - cobra.OnInitialize(initAppConfig) - cobra.OnInitialize(initLogging) - cobra.OnInitialize(logAppConfig) + cobra.OnInitialize( + initAppConfig, + initLogging, + logAppConfig, + initEventBus, + ) } -func Execute() { - if err := rootCmd.Execute(); err != nil { - log.Errorf("could not start application: %w", err) - os.Exit(1) - } -} +func startWorker(userImage string) <-chan error { + errs := make(chan error) + go func() { + defer close(errs) -func runCmdWrapper(cmd *cobra.Command, args []string) { - os.Exit(doRunCmd(cmd, args)) + log.Infof("Fetching image '%s'", userImage) + img, err := stereoscope.GetImage(userImage) + if err != nil { + errs <- fmt.Errorf("could not fetch image '%s': %w", userImage, err) + return + } + defer stereoscope.Cleanup() + + log.Info("Identifying Distro") + distro := imgbom.IdentifyDistro(img) + if distro == nil { + log.Errorf("error identifying distro") + } else { + log.Infof(" Distro: %s", distro) + } + + log.Info("Cataloging image") + catalog, err := imgbom.CatalogImage(img, appConfig.ScopeOpt) + if err != nil { + errs <- fmt.Errorf("could not catalog image: %w", err) + } + + log.Info("Complete!") + bus.Publish(partybus.Event{ + Type: event.CatalogerFinished, + Value: presenter.GetPresenter(appConfig.PresenterOpt, img, catalog), + }) + }() + return errs } func doRunCmd(_ *cobra.Command, args []string) int { - userImageStr := args[0] - log.Infof("Fetching image '%s'", userImageStr) - img, err := stereoscope.GetImage(userImageStr) - if err != nil { - log.Errorf("could not fetch image '%s': %w", userImageStr, err) - return 1 - } - defer stereoscope.Cleanup() + errs := startWorker(args[0]) - log.Info("Identifying Distro") - distro := imgbom.IdentifyDistro(img) - if distro == nil { - log.Errorf("error identifying distro") - } else { - log.Infof(" Distro: %s", distro) - } + ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet) - log.Info("Cataloging image") - catalog, err := imgbom.CatalogImage(img, appConfig.ScopeOpt) - if err != nil { - log.Errorf("could not catalog image: %w", err) - return 1 - } - - log.Info("Complete!") - err = presenter.GetPresenter(appConfig.PresenterOpt).Present(os.Stdout, img, catalog) - if err != nil { - log.Errorf("could not format catalog results: %w", err) - return 1 - } - - return 0 + return ux(errs, eventSubscription) } diff --git a/go.mod b/go.mod index da9a4d174..eaa1857dc 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,30 @@ module github.com/anchore/imgbom go 1.14 require ( + github.com/Microsoft/hcsshim v0.8.9 // indirect github.com/adrg/xdg v0.2.1 github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe - github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b - github.com/aquasecurity/go-dep-parser v0.0.0-20200123140603-4dc0125084da + github.com/anchore/stereoscope v0.0.0-20200624175800-ef5dbfb7cae4 + github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect github.com/go-test/deep v1.0.6 github.com/google/go-containerregistry v0.1.1 // indirect + github.com/gookit/color v1.2.5 github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 // indirect github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-version v1.2.0 - github.com/mgenware/go-glob v0.0.0-20170209052720-699af28aac4e // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.1 + github.com/opencontainers/runc v0.1.1 // indirect github.com/sergi/go-diff v1.1.0 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-20200621153512-2778c704bf22 + github.com/wagoodman/jotframe v0.0.0-20200622123948-2995cbd43525 go.uber.org/zap v1.15.0 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect + golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect - google.golang.org/appengine v1.6.6 google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 // indirect - google.golang.org/protobuf v1.24.0 // indirect gopkg.in/ini.v1 v1.57.0 // indirect gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index e677bd73e..667cffff7 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,7 @@ github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced3 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -105,22 +106,16 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe h1:YMXe4RA3qy4Ri5fmGQii/Gn+Pxv3oBfiS/LqzeOVuwo= github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU= -github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e h1:QBwtrM0MXi0z+GcHk3RoSyzaQ+CLgas0bC/uOd1P+PQ= github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g= -github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5 h1:eViCIr4O1e4M93nbbMZdrRW0JjqDjPYdtMXEOC3jQQQ= -github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5/go.mod h1:OeCrFeSu8+p02qC7u9/u8wBOh50VQa8eHJjXVuANvLo= -github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6 h1:Fu779yw004jyFH1UkQD8lTf0GmGRfrOQIK5QiqmIwU8= -github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ= -github.com/anchore/stereoscope v0.0.0-20200612195212-342a44f79c65 h1:wghtT1rUItLg/gx/LhMx6fYKJwnUGpfXvcA8WGWM/co= -github.com/anchore/stereoscope v0.0.0-20200612195212-342a44f79c65/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ= -github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b h1:LmFKsQi4oj2VJjch7JhQNzJg1A56FjwHqWZz1ZZKgIw= github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ= +github.com/anchore/stereoscope v0.0.0-20200624175800-ef5dbfb7cae4 h1:bPd6YFo9VDyoTLVcawFNbW9Z8dQA3M/pCgdD22dR0VQ= +github.com/anchore/stereoscope v0.0.0-20200624175800-ef5dbfb7cae4/go.mod h1:f4LZpPnN/5RpQnzcznDsYNeYavFCAW8CpbHN01G3Lh8= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= -github.com/aquasecurity/go-dep-parser v0.0.0-20200123140603-4dc0125084da/go.mod h1:X42mTIRhgPalSm81Om2kD+3ydeunbC8TZtZj1bvgRo8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -160,10 +155,12 @@ github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqh github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.4 h1:3o0smo5SKY7H6AJCmJhsnCjR2/V2T8VmiHt7seN2/kI= github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb h1:nXPkFq8X1a9ycY3GYQpFNxHh3j2JgY7zDZfq2EXMIzk= github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= @@ -187,6 +184,7 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -199,9 +197,9 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg= github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= @@ -331,9 +329,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.0.0-20200430153450-5cbd060f5c92/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA= -github.com/google/go-containerregistry v0.0.0-20200601195303-96cf69f03a3c/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM= github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g= github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM= github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws= @@ -356,6 +354,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= @@ -365,12 +364,16 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gookit/color v1.2.5 h1:s1gzb/fg3HhkSLKyWVUsZcVBUo+R1TwEYTmmxH8gGFg= +github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904= github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -440,8 +443,11 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -460,8 +466,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -488,8 +494,10 @@ github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 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.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-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= @@ -498,7 +506,6 @@ 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/mgenware/go-glob v0.0.0-20170209052720-699af28aac4e/go.mod h1:xLzoKsPridReLT8FAL9Ja/Tk3nk1YiUbeEhRnnxoej8= 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= @@ -518,6 +525,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= @@ -527,6 +535,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -550,6 +559,7 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -565,6 +575,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= @@ -612,14 +623,17 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOms github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -659,6 +673,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -691,6 +706,14 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +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 h1:lV3ioFpbqvfZ1bXSQfloLWzom1OPU/5UjyU0wmBlkNc= +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/jotframe v0.0.0-20200622123948-2995cbd43525 h1:fGlwSBQrl9/axciK2+gJ9q86SeQYJpbPx4vOrExvZXY= +github.com/wagoodman/jotframe v0.0.0-20200622123948-2995cbd43525/go.mod h1:DzXZ1wfRedNhC3KQTick8Gf3CEPMFHsP5k4R/ldjKtw= 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= @@ -713,6 +736,7 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= @@ -723,6 +747,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -732,7 +757,10 @@ golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -756,6 +784,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -764,6 +793,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -818,6 +848,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -831,6 +862,7 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -882,6 +914,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -950,9 +983,11 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d h1:SR+e35rACZFBohNb4Om1ibX6N3iO0FtdbwqGSuD9dBU= golang.org/x/tools v0.0.0-20200527183253-8e7acdbce89d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= @@ -1011,8 +1046,6 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200603110839-e855014d5736 h1:+IE3xTD+6Eb7QWG5JFp+dQr/XjKpjmrNkh4pdjTdHEs= -google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w= google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 h1:1N7l1PuXZwEK7OhHdmKQROOM75PnUjABGwvVRbLBgFk= @@ -1046,6 +1079,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1072,7 +1106,9 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1080,6 +1116,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= diff --git a/imgbom/cataloger/controller.go b/imgbom/cataloger/controller.go index ed20b3eb3..cf4197453 100644 --- a/imgbom/cataloger/controller.go +++ b/imgbom/cataloger/controller.go @@ -4,11 +4,15 @@ import ( "github.com/anchore/imgbom/imgbom/cataloger/bundler" "github.com/anchore/imgbom/imgbom/cataloger/dpkg" "github.com/anchore/imgbom/imgbom/cataloger/python" + "github.com/anchore/imgbom/imgbom/event" "github.com/anchore/imgbom/imgbom/pkg" "github.com/anchore/imgbom/imgbom/scope" + "github.com/anchore/imgbom/internal/bus" "github.com/anchore/imgbom/internal/log" "github.com/anchore/stereoscope/pkg/file" "github.com/hashicorp/go-multierror" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" ) var controllerInstance controller @@ -48,14 +52,36 @@ func (c *controller) add(a Cataloger) { c.catalogers = append(c.catalogers, a) } +type Monitor struct { + FilesProcessed progress.Monitorable + PackagesDiscovered progress.Monitorable +} + +func (c *controller) trackCataloger() (*progress.Manual, *progress.Manual) { + filesProcessed := progress.Manual{} + packagesDiscovered := progress.Manual{} + + bus.Publish(partybus.Event{ + Type: event.CatalogerStarted, + Value: Monitor{ + FilesProcessed: progress.Monitorable(&filesProcessed), + PackagesDiscovered: progress.Monitorable(&packagesDiscovered), + }, + }) + return &filesProcessed, &packagesDiscovered +} + func (c *controller) catalog(s scope.Scope) (*pkg.Catalog, error) { catalog := pkg.NewCatalog() fileSelection := make([]file.Reference, 0) + filesProcessed, packagesDiscovered := c.trackCataloger() + // ask catalogers for files to extract from the image tar for _, a := range c.catalogers { fileSelection = append(fileSelection, a.SelectFiles(&s)...) log.Debugf("cataloger '%s' selected '%d' files", a.Name(), len(fileSelection)) + filesProcessed.N += int64(len(fileSelection)) } // fetch contents for requested selection by catalogers @@ -75,6 +101,7 @@ func (c *controller) catalog(s scope.Scope) (*pkg.Catalog, error) { } log.Debugf("cataloger '%s' discovered '%d' packages", a.Name(), len(packages)) + packagesDiscovered.N += int64(len(packages)) for _, p := range packages { catalog.Add(p) @@ -85,5 +112,8 @@ func (c *controller) catalog(s scope.Scope) (*pkg.Catalog, error) { return nil, errs } + filesProcessed.SetCompleted() + packagesDiscovered.SetCompleted() + return catalog, nil } diff --git a/imgbom/event/event.go b/imgbom/event/event.go new file mode 100644 index 000000000..910eaa806 --- /dev/null +++ b/imgbom/event/event.go @@ -0,0 +1,8 @@ +package event + +import "github.com/wagoodman/go-partybus" + +const ( + CatalogerStarted partybus.EventType = "cataloger-started-event" + CatalogerFinished partybus.EventType = "cataloger-finished-event" +) diff --git a/imgbom/event/parsers/parsers.go b/imgbom/event/parsers/parsers.go new file mode 100644 index 000000000..6795c3a47 --- /dev/null +++ b/imgbom/event/parsers/parsers.go @@ -0,0 +1,61 @@ +package parsers + +import ( + "fmt" + + "github.com/anchore/imgbom/imgbom/cataloger" + "github.com/anchore/imgbom/imgbom/event" + "github.com/anchore/imgbom/imgbom/presenter" + "github.com/wagoodman/go-partybus" +) + +type ErrBadPayload struct { + Type partybus.EventType + Field string + Value interface{} +} + +func (e *ErrBadPayload) Error() string { + return fmt.Sprintf("event='%s' has bad event payload field='%v': '%+v'", string(e.Type), e.Field, e.Value) +} + +func newPayloadErr(t partybus.EventType, field string, value interface{}) error { + return &ErrBadPayload{ + Type: t, + Field: field, + Value: value, + } +} + +func checkEventType(actual, expected partybus.EventType) error { + if actual != expected { + return newPayloadErr(expected, "Type", actual) + } + return nil +} + +func ParseCatalogerStarted(e partybus.Event) (*cataloger.Monitor, error) { + if err := checkEventType(e.Type, event.CatalogerStarted); err != nil { + return nil, err + } + + monitor, ok := e.Value.(cataloger.Monitor) + if !ok { + return nil, newPayloadErr(e.Type, "Value", e.Value) + } + + return &monitor, nil +} + +func ParseCatalogerFinished(e partybus.Event) (presenter.Presenter, error) { + if err := checkEventType(e.Type, event.CatalogerFinished); err != nil { + return nil, err + } + + pres, ok := e.Value.(presenter.Presenter) + if !ok { + return nil, newPayloadErr(e.Type, "Value", e.Value) + } + + return pres, nil +} diff --git a/imgbom/lib.go b/imgbom/lib.go index c082c9614..d0368a2f5 100644 --- a/imgbom/lib.go +++ b/imgbom/lib.go @@ -6,8 +6,10 @@ import ( "github.com/anchore/imgbom/imgbom/logger" "github.com/anchore/imgbom/imgbom/pkg" "github.com/anchore/imgbom/imgbom/scope" + "github.com/anchore/imgbom/internal/bus" "github.com/anchore/imgbom/internal/log" "github.com/anchore/stereoscope/pkg/image" + "github.com/wagoodman/go-partybus" ) func IdentifyDistro(img *image.Image) *distro.Distro { @@ -26,3 +28,7 @@ func CatalogImage(img *image.Image, o scope.Option) (*pkg.Catalog, error) { func SetLogger(logger logger.Logger) { log.Log = logger } + +func SetBus(b *partybus.Bus) { + bus.SetPublisher(b) +} diff --git a/imgbom/presenter/json/presenter.go b/imgbom/presenter/json/presenter.go index be27e07c9..995b36f98 100644 --- a/imgbom/presenter/json/presenter.go +++ b/imgbom/presenter/json/presenter.go @@ -9,10 +9,16 @@ import ( stereoscopeImg "github.com/anchore/stereoscope/pkg/image" ) -type Presenter struct{} +type Presenter struct { + img *stereoscopeImg.Image + catalog *pkg.Catalog +} -func NewPresenter() *Presenter { - return &Presenter{} +func NewPresenter(img *stereoscopeImg.Image, catalog *pkg.Catalog) *Presenter { + return &Presenter{ + img: img, + catalog: catalog, + } } type document struct { @@ -49,25 +55,25 @@ type artifact struct { Metadata interface{} `json:"metadata"` } -func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, catalog *pkg.Catalog) error { - tags := make([]string, len(img.Metadata.Tags)) - for idx, tag := range img.Metadata.Tags { +func (pres *Presenter) Present(output io.Writer) error { + tags := make([]string, len(pres.img.Metadata.Tags)) + for idx, tag := range pres.img.Metadata.Tags { tags[idx] = tag.String() } doc := document{ Image: image{ - Digest: img.Metadata.Digest, - Size: img.Metadata.Size, - MediaType: string(img.Metadata.MediaType), + Digest: pres.img.Metadata.Digest, + Size: pres.img.Metadata.Size, + MediaType: string(pres.img.Metadata.MediaType), Tags: tags, - Layers: make([]layer, len(img.Layers)), + Layers: make([]layer, len(pres.img.Layers)), }, Artifacts: make([]artifact, 0), } // populate image... - for idx, l := range img.Layers { + for idx, l := range pres.img.Layers { doc.Image.Layers[idx] = layer{ MediaType: string(l.Metadata.MediaType), Digest: l.Metadata.Digest, @@ -76,7 +82,7 @@ func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, cata } // populate artifacts... - for p := range catalog.Enumerate() { + for p := range pres.catalog.Enumerate() { art := artifact{ Name: p.Name, Version: p.Version, @@ -86,7 +92,7 @@ func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, cata } for idx, src := range p.Source { - fileMetadata, err := img.FileCatalog.Get(src) + fileMetadata, err := pres.img.FileCatalog.Get(src) if err != nil { // TODO: test case log.Errorf("could not get metadata from catalog (presenter=json): %+v", src) diff --git a/imgbom/presenter/json/presenter_test.go b/imgbom/presenter/json/presenter_test.go index ba78367b5..cb04a33ac 100644 --- a/imgbom/presenter/json/presenter_test.go +++ b/imgbom/presenter/json/presenter_test.go @@ -36,7 +36,6 @@ var update = flag.Bool("update", false, "update the *.golden files for json pres // } func TestJsonPresenter(t *testing.T) { - pres := NewPresenter() var buffer bytes.Buffer testImage := "image-simple" @@ -66,8 +65,10 @@ func TestJsonPresenter(t *testing.T) { Type: pkg.DebPkg, }) + pres := NewPresenter(img, catalog) + // run presenter - err := pres.Present(&buffer, img, catalog) + err := pres.Present(&buffer) if err != nil { t.Fatal(err) } diff --git a/imgbom/presenter/presenter.go b/imgbom/presenter/presenter.go index 421618985..6a51c5e74 100644 --- a/imgbom/presenter/presenter.go +++ b/imgbom/presenter/presenter.go @@ -10,15 +10,16 @@ import ( ) type Presenter interface { - Present(io.Writer, *image.Image, *pkg.Catalog) error + Present(io.Writer) error } -func GetPresenter(option Option) Presenter { +func GetPresenter(option Option, img *image.Image, catalog *pkg.Catalog) Presenter { switch option { case JSONPresenter: - return json.NewPresenter() + return json.NewPresenter(img, catalog) case TextPresenter: - return text.NewPresenter() + return text.NewPresenter(img, catalog) + default: return nil } diff --git a/imgbom/presenter/text/presenter.go b/imgbom/presenter/text/presenter.go index 016354526..ef7531105 100644 --- a/imgbom/presenter/text/presenter.go +++ b/imgbom/presenter/text/presenter.go @@ -10,17 +10,23 @@ import ( ) // Presenter holds the Present method to produce output -type Presenter struct{} +type Presenter struct { + img *stereoscopeImg.Image + catalog *pkg.Catalog +} // NewPresenter is a constructor for a Presenter -func NewPresenter() *Presenter { - return &Presenter{} +func NewPresenter(img *stereoscopeImg.Image, catalog *pkg.Catalog) *Presenter { + return &Presenter{ + img: img, + catalog: catalog, + } } // Present is a method that is in charge of writing to an output buffer -func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, catalog *pkg.Catalog) error { - tags := make([]string, len(img.Metadata.Tags)) - for idx, tag := range img.Metadata.Tags { +func (pres *Presenter) Present(output io.Writer) error { + tags := make([]string, len(pres.img.Metadata.Tags)) + for idx, tag := range pres.img.Metadata.Tags { tags[idx] = tag.String() } @@ -30,7 +36,7 @@ func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, cata fmt.Fprintln(w, "[Image]") - for idx, l := range img.Layers { + for idx, l := range pres.img.Layers { fmt.Fprintln(w, " Layer:\t", idx) fmt.Fprintln(w, " Digest:\t", l.Metadata.Digest) fmt.Fprintln(w, " Size:\t", l.Metadata.Size) @@ -40,7 +46,7 @@ func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, cata } // populate artifacts... - for p := range catalog.Enumerate() { + for p := range pres.catalog.Enumerate() { fmt.Fprintln(w, fmt.Sprintf("[%s]", p.Name)) fmt.Fprintln(w, " Version:\t", p.Version) fmt.Fprintln(w, " Type:\t", p.Type.String()) diff --git a/imgbom/presenter/text/presenter_test.go b/imgbom/presenter/text/presenter_test.go index 797424b51..9b3feaeab 100644 --- a/imgbom/presenter/text/presenter_test.go +++ b/imgbom/presenter/text/presenter_test.go @@ -19,7 +19,6 @@ type PackageInfo struct { } func TestTextPresenter(t *testing.T) { - pres := NewPresenter() var buffer bytes.Buffer catalog := pkg.NewCatalog() @@ -53,8 +52,9 @@ func TestTextPresenter(t *testing.T) { l.Metadata.Digest = "sha256:ad8ecdc058976c07e7e347cb89fa9ad86a294b5ceaae6d09713fb035f84115abf3c4a2388a4af3aa60f13b94f4c6846930bdf53" } + pres := NewPresenter(img, catalog) // run presenter - err := pres.Present(&buffer, img, catalog) + err := pres.Present(&buffer) if err != nil { t.Fatal(err) } diff --git a/internal/bus/bus.go b/internal/bus/bus.go new file mode 100644 index 000000000..fdbca5062 --- /dev/null +++ b/internal/bus/bus.go @@ -0,0 +1,19 @@ +package bus + +import "github.com/wagoodman/go-partybus" + +var publisher partybus.Publisher +var active bool + +func SetPublisher(p partybus.Publisher) { + publisher = p + if p != nil { + active = true + } +} + +func Publish(event partybus.Event) { + if active { + publisher.Publish(event) + } +} diff --git a/internal/logger/zap.go b/internal/logger/zap.go index 10e650678..219ba5519 100644 --- a/internal/logger/zap.go +++ b/internal/logger/zap.go @@ -79,7 +79,7 @@ func (l *ZapLogger) getConsoleEncoder(config LogConfig) zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() if config.Structured { encoderConfig.EncodeName = zapcore.FullNameEncoder - encoderConfig.EncodeCaller = zapcore.FullCallerEncoder + encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder return zapcore.NewJSONEncoder(encoderConfig) } encoderConfig.EncodeTime = nil diff --git a/internal/ui/common/event_handlers.go b/internal/ui/common/event_handlers.go new file mode 100644 index 000000000..75f688010 --- /dev/null +++ b/internal/ui/common/event_handlers.go @@ -0,0 +1,22 @@ +package common + +import ( + "fmt" + "os" + + imgbomEventParsers "github.com/anchore/imgbom/imgbom/event/parsers" + "github.com/wagoodman/go-partybus" +) + +func CatalogerFinishedHandler(event partybus.Event) error { + // show the report to stdout + pres, err := imgbomEventParsers.ParseCatalogerFinished(event) + if err != nil { + return fmt.Errorf("bad CatalogerFinished event: %w", err) + } + + if err := pres.Present(os.Stdout); err != nil { + return fmt.Errorf("unable to show package catalog report: %w", err) + } + return nil +} diff --git a/internal/ui/etui/ephemeral_tui.go b/internal/ui/etui/ephemeral_tui.go new file mode 100644 index 000000000..ccd864258 --- /dev/null +++ b/internal/ui/etui/ephemeral_tui.go @@ -0,0 +1,102 @@ +package etui + +import ( + "context" + "fmt" + "os" + "sync" + + imgbomEvent "github.com/anchore/imgbom/imgbom/event" + "github.com/anchore/imgbom/internal/log" + "github.com/anchore/imgbom/internal/ui/common" + stereoscopeEvent "github.com/anchore/stereoscope/pkg/event" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/jotframe/pkg/frame" +) + +// TODO: specify per-platform implementations with build tags + +func setupScreen(output *os.File) *frame.Frame { + config := frame.Config{ + PositionPolicy: frame.PolicyFloatForward, + // only report output to stderr, reserve report output for stdout + Output: output, + } + + fr, err := frame.New(config) + if err != nil { + log.Errorf("failed to create screen object: %+v", err) + return nil + } + return fr +} + +func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscription) int { + output := os.Stderr + + // hide cursor + _, _ = fmt.Fprint(output, "\x1b[?25l") + // show cursor + defer fmt.Fprint(output, "\x1b[?25h") + + fr := setupScreen(output) + if fr == nil { + return 1 + } + + var err error + var wg = &sync.WaitGroup{} + events := subscription.Events() + ctx := context.Background() + +eventLoop: + for { + select { + case e, ok := <-events: + if !ok { + // is this unexpected? if so should we indicate this? + break eventLoop + } + switch e.Type { + case stereoscopeEvent.ReadImage: + err = imageReadHandler(ctx, fr, e, wg) + if err != nil { + log.Errorf("unable to show read image event: %+v", err) + } + + case stereoscopeEvent.FetchImage: + err = imageFetchHandler(ctx, fr, e, wg) + if err != nil { + log.Errorf("unable to show fetch image event: %+v", err) + } + + case imgbomEvent.CatalogerStarted: + err = catalogerStartedHandler(ctx, fr, e, wg) + if err != nil { + log.Errorf("unable to show catalog image start event: %+v", err) + } + case imgbomEvent.CatalogerFinished: + // we may have other background processes still displaying progress, wait for them to + // finish before discontinuing dynamic content and showing the final report + wg.Wait() + frame.Close() + fmt.Println() + + err := common.CatalogerFinishedHandler(e) + if err != nil { + log.Errorf("unable to show catalog image finished event: %+v", err) + } + + // this is the last expected event + break eventLoop + } + case <-ctx.Done(): + if ctx.Err() != nil { + log.Errorf("cancelled (%+v)", err) + } + break eventLoop + } + } + + return 0 +} diff --git a/internal/ui/etui/event_handlers.go b/internal/ui/etui/event_handlers.go new file mode 100644 index 000000000..449a39c14 --- /dev/null +++ b/internal/ui/etui/event_handlers.go @@ -0,0 +1,145 @@ +package etui + +import ( + "context" + "fmt" + "io" + "sync" + "time" + + imgbomEventParsers "github.com/anchore/imgbom/imgbom/event/parsers" + stereoEventParsers "github.com/anchore/stereoscope/pkg/event/parsers" + "github.com/gookit/color" + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + "github.com/wagoodman/go-progress/format" + "github.com/wagoodman/jotframe/pkg/frame" +) + +const maxBarWidth = 50 +const statusSet = SpinnerDotSet // SpinnerCircleOutlineSet +const completedStatus = "✔" //"●" +const tileFormat = color.Bold +const statusTitleTemplate = " %s %-28s " + +var auxInfoFormat = color.HEX("#777777") + +func startProcess() (format.Simple, *Spinner) { + width, _ := frame.GetTerminalSize() + barWidth := int(0.25 * float64(width)) + if barWidth > maxBarWidth { + barWidth = maxBarWidth + } + formatter := format.NewSimpleWithTheme(barWidth, format.HeavyNoBarTheme, format.ColorCompleted, format.ColorTodo) + spinner := NewSpinner(statusSet) + + return formatter, &spinner +} + +func imageFetchHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + _, prog, err := stereoEventParsers.ParseFetchImage(event) + if err != nil { + return fmt.Errorf("bad FetchImage event: %w", err) + } + + line, err := fr.Append() + if err != nil { + return err + } + + wg.Add(1) + + go func() { + defer wg.Done() + formatter, spinner := startProcess() + stream := progress.Stream(ctx, prog, 150*time.Millisecond) + title := tileFormat.Sprint("Fetching image...") + + for p := range stream { + progStr, err := formatter.Format(p) + spin := color.Magenta.Sprint(spinner.Next()) + if err != nil { + _, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err)) + } else { + auxInfo := auxInfoFormat.Sprintf("[%s]", prog.Stage()) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s %s", spin, title, progStr, auxInfo)) + } + } + + spin := color.Green.Sprint(completedStatus) + title = tileFormat.Sprint("Fetched image") + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + }() + return err +} + +func imageReadHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + _, prog, err := stereoEventParsers.ParseReadImage(event) + if err != nil { + return fmt.Errorf("bad ReadImage event: %w", err) + } + + line, err := fr.Append() + if err != nil { + return err + } + + wg.Add(1) + + go func() { + defer wg.Done() + formatter, spinner := startProcess() + stream := progress.Stream(ctx, prog, 150*time.Millisecond) + title := tileFormat.Sprint("Reading image...") + + for p := range stream { + progStr, err := formatter.Format(p) + spin := color.Magenta.Sprint(spinner.Next()) + if err != nil { + _, _ = io.WriteString(line, fmt.Sprintf("Error: %+v", err)) + } else { + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, progStr)) + } + } + + spin := color.Green.Sprint(completedStatus) + title = tileFormat.Sprint("Read image") + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate, spin, title)) + }() + + return nil +} + +func catalogerStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error { + monitor, err := imgbomEventParsers.ParseCatalogerStarted(event) + if err != nil { + return fmt.Errorf("bad CatalogerStarted event: %w", err) + } + + line, err := fr.Append() + if err != nil { + return err + } + + wg.Add(1) + + go func() { + defer wg.Done() + _, spinner := startProcess() + stream := progress.StreamMonitors(ctx, []progress.Monitorable{monitor.FilesProcessed, monitor.PackagesDiscovered}, 50*time.Millisecond) + title := tileFormat.Sprint("Cataloging image...") + + for p := range stream { + spin := color.Magenta.Sprint(spinner.Next()) + auxInfo := auxInfoFormat.Sprintf("[packages %d]", p[1]) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo)) + } + + spin := color.Green.Sprint(completedStatus) + title = tileFormat.Sprint("Cataloged image") + auxInfo := auxInfoFormat.Sprintf("[%d packages]", monitor.PackagesDiscovered.Current()) + _, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo)) + }() + + return nil +} diff --git a/internal/ui/etui/spinner.go b/internal/ui/etui/spinner.go new file mode 100644 index 000000000..0f4458035 --- /dev/null +++ b/internal/ui/etui/spinner.go @@ -0,0 +1,72 @@ +package etui + +import ( + "strings" + "sync" +) + +// TODO: move me to a common module (used in multiple repos) + +const ( + SpinnerCircleOutlineSet = "◜◠◯◎◉●◉◎◯◡◞" + SpinnerCircleSet = "◌◯◎◉●◉◎◯" + SpinnerDotSet = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" + SpinnerHorizontalBarSet = "▉▊▋▌▍▎▏▎▍▌▋▊▉" + SpinnerVerticalBarSet = "▁▃▄▅▆▇█▇▆▅▄▃▁" + SpinnerDoubleBarSet = "▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁" + SpinnerArrowSet = "←↖↑↗→↘↓↙" +) + +var SpinnerCircleDotSet = []string{ + "⠈⠁", + "⠈⠑", + "⠈⠱", + "⠈⡱", + "⢀⡱", + "⢄⡱", + "⢄⡱", + "⢆⡱", + "⢎⡱", + "⢎⡰", + "⢎⡠", + "⢎⡀", + "⢎⠁", + "⠎⠁", + "⠊⠁", +} + +type Spinner struct { + index int + charset []string + lock sync.Mutex +} + +func NewSpinner(charset string) Spinner { + return Spinner{ + charset: strings.Split(charset, ""), + } +} + +func NewSpinnerFromSlice(charset []string) Spinner { + return Spinner{ + charset: charset, + } +} + +func (s *Spinner) Current() string { + s.lock.Lock() + defer s.lock.Unlock() + + return s.charset[s.index] +} + +func (s *Spinner) Next() string { + s.lock.Lock() + defer s.lock.Unlock() + c := s.charset[s.index] + s.index++ + if s.index >= len(s.charset) { + s.index = 0 + } + return c +} diff --git a/internal/ui/logger_output.go b/internal/ui/logger_output.go new file mode 100644 index 000000000..ab3bc2462 --- /dev/null +++ b/internal/ui/logger_output.go @@ -0,0 +1,42 @@ +package ui + +import ( + imgbomEvent "github.com/anchore/imgbom/imgbom/event" + "github.com/anchore/imgbom/internal/log" + "github.com/anchore/imgbom/internal/ui/common" + "github.com/wagoodman/go-partybus" +) + +func LoggerUI(workerErrs <-chan error, subscription *partybus.Subscription) int { + var returnCode int + + events := subscription.Events() +eventLoop: + for { + select { + case err := <-workerErrs: + if err != nil { + log.Errorf(err.Error()) + returnCode = 1 + } + case e, ok := <-events: + if !ok { + // event bus closed... + break eventLoop + } + + // ignore all events except for the final event + if e.Type == imgbomEvent.CatalogerFinished { + err := common.CatalogerFinishedHandler(e) + if err != nil { + log.Errorf("unable to show catalog image finished event: %+v", err) + } + + // this is the last expected event + break eventLoop + } + } + } + + return returnCode +} diff --git a/internal/ui/select.go b/internal/ui/select.go new file mode 100644 index 000000000..2d77435c9 --- /dev/null +++ b/internal/ui/select.go @@ -0,0 +1,28 @@ +package ui + +import ( + "os" + "runtime" + + "github.com/anchore/imgbom/internal/ui/etui" + "golang.org/x/crypto/ssh/terminal" +) + +// TODO: build tags to exclude options from windows + +func Select(verbose, quiet bool) UI { + var ui UI + + isStdoutATty := terminal.IsTerminal(int(os.Stdout.Fd())) + isStderrATty := terminal.IsTerminal(int(os.Stderr.Fd())) + notATerminal := !isStderrATty && !isStdoutATty + + switch { + case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty: + ui = LoggerUI + default: + ui = etui.OutputToEphemeralTUI + } + + return ui +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go new file mode 100644 index 000000000..855e7e32d --- /dev/null +++ b/internal/ui/ui.go @@ -0,0 +1,7 @@ +package ui + +import ( + "github.com/wagoodman/go-partybus" +) + +type UI func(<-chan error, *partybus.Subscription) int