mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
Port UI to bubbletea (#1385)
* initial port to bubbletea Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove jotframe UI Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add bubbletea component tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update main.go refs to cmd package Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * move goreleaser build dir to cmd Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * upgrade yardstick for grype source installs and fix post-ui tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * ensure stable severity map in UI component test Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add windows support for tui Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
37f436cfb6
commit
ebd4643930
70 changed files with 1654 additions and 848 deletions
|
@ -9,6 +9,7 @@ env:
|
|||
|
||||
builds:
|
||||
- id: linux-build
|
||||
dir: ./cmd/grype
|
||||
binary: grype
|
||||
goos:
|
||||
- linux
|
||||
|
@ -29,6 +30,7 @@ builds:
|
|||
-X github.com/anchore/grype/internal/version.gitDescription={{.Summary}}
|
||||
|
||||
- id: darwin-build
|
||||
dir: ./cmd/grype
|
||||
binary: grype
|
||||
goos:
|
||||
- darwin
|
||||
|
@ -44,6 +46,7 @@ builds:
|
|||
- QUILL_LOG_FILE=/tmp/quill-{{ .Target }}.log
|
||||
|
||||
- id: windows-build
|
||||
dir: ./cmd/grype
|
||||
binary: grype
|
||||
goos:
|
||||
- windows
|
||||
|
|
|
@ -6,7 +6,7 @@ There are a few useful things to know before diving into the codebase. This proj
|
|||
|
||||
After cloning do the following:
|
||||
|
||||
1. run `go build main.go` to get a binary named `main` from the source (use `-o <name>` to get a differently named binary), or optionally `go run main.go` to run from source.
|
||||
1. run `go build ./cmd/grype` to get a binary named `main` from the source (use `-o <name>` to get a differently named binary), or optionally `go run ./cmd/grype` to run from source.
|
||||
|
||||
In order to run tests and build all artifacts:
|
||||
|
||||
|
@ -31,7 +31,7 @@ to a released version (e.g. `go get github.com/anchore/syft@v<semantic-version>`
|
|||
The currently supported database format is Sqlite3. Install `sqlite3` in your system and ensure that the `sqlite3` executable is available in your path. Ask `grype` about the location of the database, which will be different depending on the operating system:
|
||||
|
||||
```
|
||||
$ go run main.go db status
|
||||
$ go run ./cmd/grype db status
|
||||
Location: /Users/alfredo/Library/Caches/grype/db
|
||||
Built: 2020-07-31 08:18:29 +0000 UTC
|
||||
Current DB Version: 1
|
||||
|
|
|
@ -446,7 +446,7 @@ Find complete information on Grype's database commands by running `grype db --he
|
|||
Grype supplies shell completion through its CLI implementation ([cobra](https://github.com/spf13/cobra/blob/master/shell_completions.md)). Generate the completion code for your shell by running one of the following commands:
|
||||
|
||||
- `grype completion <bash|zsh|fish>`
|
||||
- `go run main.go completion <bash|zsh|fish>`
|
||||
- `go run ./cmd/grype completion <bash|zsh|fish>`
|
||||
|
||||
This will output a shell script to STDOUT, which can then be used as a completion script for Grype. Running one of the above commands with the
|
||||
`-h` or `--help` flags will provide instructions on how to do that for your chosen shell.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,16 +1,15 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/grype/cmd/grype/internal/ui"
|
||||
"github.com/anchore/grype/grype/db"
|
||||
"github.com/anchore/grype/grype/differ"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/grype/internal/ui"
|
||||
"github.com/anchore/stereoscope"
|
||||
)
|
||||
|
||||
|
@ -59,15 +58,19 @@ func startDBDiffCmd(base string, target string, deleteDatabases bool) <-chan err
|
|||
return
|
||||
}
|
||||
|
||||
sb := &strings.Builder{}
|
||||
|
||||
if len(*diff) == 0 {
|
||||
fmt.Println("Databases are identical!")
|
||||
sb.WriteString("Databases are identical!\n")
|
||||
} else {
|
||||
err := d.Present(dbDiffOutputFormat, diff, os.Stdout)
|
||||
err := d.Present(dbDiffOutputFormat, diff, sb)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
}
|
||||
}
|
||||
|
||||
bus.Report(sb.String())
|
||||
|
||||
if deleteDatabases {
|
||||
errs <- d.DeleteDatabases()
|
||||
}
|
||||
|
@ -76,16 +79,6 @@ func startDBDiffCmd(base string, target string, deleteDatabases bool) <-chan err
|
|||
}
|
||||
|
||||
func runDBDiffCmd(cmd *cobra.Command, args []string) error {
|
||||
reporter, closer, err := reportWriter()
|
||||
defer func() {
|
||||
if err := closer(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %+v", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deleteDatabases, err := cmd.Flags().GetBool(deleteFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -117,7 +110,7 @@ func runDBDiffCmd(cmd *cobra.Command, args []string) error {
|
|||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
ui.Select(isVerbose(), appConfig.Quiet)...,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,14 +1,13 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/grype/cmd/grype/internal/ui"
|
||||
"github.com/anchore/grype/grype/db"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/grype/internal/ui"
|
||||
"github.com/anchore/stereoscope"
|
||||
)
|
||||
|
||||
|
@ -50,20 +49,11 @@ func startDBUpdateCmd() <-chan error {
|
|||
}
|
||||
|
||||
func runDBUpdateCmd(_ *cobra.Command, _ []string) error {
|
||||
reporter, closer, err := reportWriter()
|
||||
defer func() {
|
||||
if err := closer(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %+v", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return eventLoop(
|
||||
startDBUpdateCmd(),
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
ui.Select(isVerbose(), appConfig.Quiet)...,
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -8,20 +8,20 @@ import (
|
|||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/grype/internal/ui"
|
||||
)
|
||||
|
||||
// eventLoop listens to worker errors (from execution path), worker events (from a partybus subscription), and
|
||||
// signal interrupts. Is responsible for handling each event relative to a given UI an to coordinate eventing until
|
||||
// an eventual graceful exit.
|
||||
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...ui.UI) error {
|
||||
func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *partybus.Subscription, cleanupFn func(), uxs ...clio.UI) error {
|
||||
defer cleanupFn()
|
||||
events := subscription.Events()
|
||||
var err error
|
||||
var ux ui.UI
|
||||
var ux clio.UI
|
||||
|
||||
if ux, err = setupUI(subscription.Unsubscribe, uxs...); err != nil {
|
||||
if ux, err = setupUI(subscription, uxs...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,9 @@ func eventLoop(workerErrs <-chan error, signals <-chan os.Signal, subscription *
|
|||
// during teardown. With the given UIs, the first UI which the ui.Setup() function does not return an error
|
||||
// will be utilized in execution. Providing a set of UIs allows for the caller to provide graceful fallbacks
|
||||
// when there are environmental problem (e.g. unable to setup a TUI with the current TTY).
|
||||
func setupUI(unsubscribe func() error, uis ...ui.UI) (ui.UI, error) {
|
||||
func setupUI(subscription *partybus.Subscription, uis ...clio.UI) (clio.UI, error) {
|
||||
for _, ux := range uis {
|
||||
if err := ux.Setup(unsubscribe); err != nil {
|
||||
if err := ux.Setup(subscription); err != nil {
|
||||
log.Warnf("unable to setup given UI, falling back to alternative UI: %+v", err)
|
||||
continue
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -11,39 +11,42 @@ import (
|
|||
"github.com/stretchr/testify/mock"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/internal/ui"
|
||||
)
|
||||
|
||||
var _ ui.UI = (*uiMock)(nil)
|
||||
var _ clio.UI = (*uiMock)(nil)
|
||||
|
||||
type uiMock struct {
|
||||
t *testing.T
|
||||
finalEvent partybus.Event
|
||||
unsubscribe func() error
|
||||
t *testing.T
|
||||
finalEvent partybus.Event
|
||||
subscription partybus.Unsubscribable
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (u *uiMock) Setup(unsubscribe func() error) error {
|
||||
func (u *uiMock) Setup(unsubscribe partybus.Unsubscribable) error {
|
||||
u.t.Helper()
|
||||
u.t.Logf("UI Setup called")
|
||||
u.unsubscribe = unsubscribe
|
||||
return u.Called(unsubscribe).Error(0)
|
||||
u.subscription = unsubscribe
|
||||
return u.Called(unsubscribe.Unsubscribe).Error(0)
|
||||
}
|
||||
|
||||
func (u *uiMock) Handle(event partybus.Event) error {
|
||||
u.t.Helper()
|
||||
u.t.Logf("UI Handle called: %+v", event.Type)
|
||||
if event == u.finalEvent {
|
||||
assert.NoError(u.t, u.unsubscribe())
|
||||
assert.NoError(u.t, u.subscription.Unsubscribe())
|
||||
}
|
||||
return u.Called(event).Error(0)
|
||||
}
|
||||
|
||||
func (u *uiMock) Teardown(_ bool) error {
|
||||
u.t.Helper()
|
||||
u.t.Logf("UI Teardown called")
|
||||
return u.Called().Error(0)
|
||||
}
|
||||
|
||||
func Test_eventLoop_gracefulExit(t *testing.T) {
|
||||
func Test_EventLoop_gracefulExit(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
||||
|
@ -110,7 +113,7 @@ func Test_eventLoop_gracefulExit(t *testing.T) {
|
|||
testWithTimeout(t, 5*time.Second, test)
|
||||
}
|
||||
|
||||
func Test_eventLoop_workerError(t *testing.T) {
|
||||
func Test_EventLoop_workerError(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
||||
|
@ -175,7 +178,7 @@ func Test_eventLoop_workerError(t *testing.T) {
|
|||
testWithTimeout(t, 5*time.Second, test)
|
||||
}
|
||||
|
||||
func Test_eventLoop_unsubscribeError(t *testing.T) {
|
||||
func Test_EventLoop_unsubscribeError(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
||||
|
@ -244,7 +247,7 @@ func Test_eventLoop_unsubscribeError(t *testing.T) {
|
|||
testWithTimeout(t, 5*time.Second, test)
|
||||
}
|
||||
|
||||
func Test_eventLoop_handlerError(t *testing.T) {
|
||||
func Test_EventLoop_handlerError(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
||||
|
@ -253,7 +256,7 @@ func Test_eventLoop_handlerError(t *testing.T) {
|
|||
|
||||
finalEvent := partybus.Event{
|
||||
Type: event.CLIExit,
|
||||
Error: fmt.Errorf("unable to create presenter"),
|
||||
Error: fmt.Errorf("an exit error occured"),
|
||||
}
|
||||
|
||||
worker := func() <-chan error {
|
||||
|
@ -316,7 +319,7 @@ func Test_eventLoop_handlerError(t *testing.T) {
|
|||
testWithTimeout(t, 5*time.Second, test)
|
||||
}
|
||||
|
||||
func Test_eventLoop_signalsStopExecution(t *testing.T) {
|
||||
func Test_EventLoop_signalsStopExecution(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
||||
|
@ -369,7 +372,7 @@ func Test_eventLoop_signalsStopExecution(t *testing.T) {
|
|||
testWithTimeout(t, 5*time.Second, test)
|
||||
}
|
||||
|
||||
func Test_eventLoop_uiTeardownError(t *testing.T) {
|
||||
func Test_EventLoop_uiTeardownError(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
|
||||
testBus := partybus.NewBus()
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/grype/cmd/grype/internal/ui"
|
||||
"github.com/anchore/grype/grype"
|
||||
"github.com/anchore/grype/grype/db"
|
||||
grypeDb "github.com/anchore/grype/grype/db/v5"
|
||||
|
@ -37,7 +38,6 @@ import (
|
|||
"github.com/anchore/grype/internal/format"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/grype/internal/stringutil"
|
||||
"github.com/anchore/grype/internal/ui"
|
||||
"github.com/anchore/grype/internal/version"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
|
@ -262,23 +262,12 @@ func rootExec(_ *cobra.Command, args []string) error {
|
|||
userInput = args[0]
|
||||
}
|
||||
|
||||
reporter, closer, err := reportWriter()
|
||||
defer func() {
|
||||
if err := closer(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return eventLoop(
|
||||
startWorker(userInput, appConfig.FailOnSeverity),
|
||||
setupSignals(),
|
||||
eventSubscription,
|
||||
stereoscope.Cleanup,
|
||||
ui.Select(isVerbose(), appConfig.Quiet, reporter)...,
|
||||
ui.Select(isVerbose(), appConfig.Quiet)...,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"os"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
8
cmd/grype/cli/ui/__snapshots__/handle_database_diff_started_test.snap
Executable file
8
cmd/grype/cli/ui/__snapshots__/handle_database_diff_started_test.snap
Executable file
|
@ -0,0 +1,8 @@
|
|||
|
||||
[TestHandler_handleDatabaseDiffStarted/DB_diff_started - 1]
|
||||
⠋ Comparing Vulnerability DBs ━━━━━━━━━━━━━━━━━━━━ [current]
|
||||
---
|
||||
|
||||
[TestHandler_handleDatabaseDiffStarted/DB_diff_complete - 1]
|
||||
✔ Compared Vulnerability DBs [20 differences found]
|
||||
---
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
[TestHandler_handleUpdateVulnerabilityDatabase/downloading_DB - 1]
|
||||
⠋ Vulnerability DB ━━━━━━━━━━━━━━━━━━━━ [current]
|
||||
---
|
||||
|
||||
[TestHandler_handleUpdateVulnerabilityDatabase/DB_download_complete - 1]
|
||||
✔ Vulnerability DB [current]
|
||||
---
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
[TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_in_progress/task_line - 1]
|
||||
⠋ Scanning for vulnerabilities [20 vulnerabilities]
|
||||
---
|
||||
|
||||
[TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_in_progress/tree - 1]
|
||||
├── 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown)
|
||||
└── 30 fixed
|
||||
---
|
||||
|
||||
[TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_complete/task_line - 1]
|
||||
✔ Scanned for vulnerabilities [25 vulnerabilities]
|
||||
---
|
||||
|
||||
[TestHandler_handleVulnerabilityScanningStarted/vulnerability_scanning_complete/tree - 1]
|
||||
├── 1 critical, 2 high, 3 medium, 4 low, 5 negligible (6 unknown)
|
||||
└── 35 fixed
|
||||
---
|
58
cmd/grype/cli/ui/handle_database_diff_started.go
Normal file
58
cmd/grype/cli/ui/handle_database_diff_started.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
type dbDiffProgressStager struct {
|
||||
monitor *monitor.DBDiff
|
||||
}
|
||||
|
||||
func (p dbDiffProgressStager) Stage() string {
|
||||
if progress.IsErrCompleted(p.monitor.StageProgress.Error()) {
|
||||
return fmt.Sprintf("%d differences found", p.monitor.DifferencesDiscovered.Current())
|
||||
}
|
||||
return p.monitor.Stager.Stage()
|
||||
}
|
||||
|
||||
func (p dbDiffProgressStager) Current() int64 {
|
||||
return p.monitor.StageProgress.Current()
|
||||
}
|
||||
|
||||
func (p dbDiffProgressStager) Error() error {
|
||||
return p.monitor.StageProgress.Error()
|
||||
}
|
||||
|
||||
func (p dbDiffProgressStager) Size() int64 {
|
||||
return p.monitor.StageProgress.Size()
|
||||
}
|
||||
|
||||
func (m *Handler) handleDatabaseDiffStarted(e partybus.Event) []tea.Model {
|
||||
mon, err := parsers.ParseDatabaseDiffingStarted(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
tsk := m.newTaskProgress(
|
||||
taskprogress.Title{
|
||||
Default: "Compare Vulnerability DBs",
|
||||
Running: "Comparing Vulnerability DBs",
|
||||
Success: "Compared Vulnerability DBs",
|
||||
},
|
||||
taskprogress.WithStagedProgressable(dbDiffProgressStager{monitor: mon}),
|
||||
)
|
||||
|
||||
tsk.HideStageOnSuccess = false
|
||||
|
||||
return []tea.Model{tsk}
|
||||
}
|
96
cmd/grype/cli/ui/handle_database_diff_started_test.go
Normal file
96
cmd/grype/cli/ui/handle_database_diff_started_test.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/gkampitakis/go-snaps/snaps"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
)
|
||||
|
||||
func TestHandler_handleDatabaseDiffStarted(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
eventFn func(*testing.T) partybus.Event
|
||||
iterations int
|
||||
}{
|
||||
{
|
||||
name: "DB diff started",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
prog := &progress.Manual{}
|
||||
prog.SetTotal(100)
|
||||
prog.Set(50)
|
||||
|
||||
diffs := &progress.Manual{}
|
||||
diffs.Set(20)
|
||||
|
||||
mon := monitor.DBDiff{
|
||||
Stager: &progress.Stage{Current: "current"},
|
||||
StageProgress: prog,
|
||||
DifferencesDiscovered: diffs,
|
||||
}
|
||||
|
||||
return partybus.Event{
|
||||
Type: event.DatabaseDiffingStarted,
|
||||
Value: mon,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DB diff complete",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
prog := &progress.Manual{}
|
||||
prog.SetTotal(100)
|
||||
prog.Set(100)
|
||||
prog.SetCompleted()
|
||||
|
||||
diffs := &progress.Manual{}
|
||||
diffs.Set(20)
|
||||
|
||||
mon := monitor.DBDiff{
|
||||
Stager: &progress.Stage{Current: "current"},
|
||||
StageProgress: prog,
|
||||
DifferencesDiscovered: diffs,
|
||||
}
|
||||
|
||||
return partybus.Event{
|
||||
Type: event.DatabaseDiffingStarted,
|
||||
Value: mon,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := tt.eventFn(t)
|
||||
handler := New(DefaultHandlerConfig())
|
||||
handler.WindowSize = tea.WindowSizeMsg{
|
||||
Width: 100,
|
||||
Height: 80,
|
||||
}
|
||||
|
||||
models := handler.Handle(e)
|
||||
require.Len(t, models, 1)
|
||||
model := models[0]
|
||||
|
||||
tsk, ok := model.(taskprogress.Model)
|
||||
require.True(t, ok)
|
||||
|
||||
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
|
||||
Time: time.Now(),
|
||||
Sequence: tsk.Sequence(),
|
||||
ID: tsk.ID(),
|
||||
})
|
||||
t.Log(got)
|
||||
snaps.MatchSnapshot(t, got)
|
||||
})
|
||||
}
|
||||
}
|
53
cmd/grype/cli/ui/handle_update_vulnerability_database.go
Normal file
53
cmd/grype/cli/ui/handle_update_vulnerability_database.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
type dbDownloadProgressStager struct {
|
||||
prog progress.StagedProgressable
|
||||
}
|
||||
|
||||
func (s dbDownloadProgressStager) Stage() string {
|
||||
stage := s.prog.Stage()
|
||||
if stage == "downloading" {
|
||||
// note: since validation is baked into the download progress there is no visibility into this stage.
|
||||
// for that reason we report "validating" on the last byte being downloaded (which tends to be the longest
|
||||
// since go-downloader is doing this work).
|
||||
if s.prog.Current() >= s.prog.Size()-1 {
|
||||
return "validating"
|
||||
}
|
||||
// show intermediate progress of the download
|
||||
return fmt.Sprintf("%s / %s", humanize.Bytes(uint64(s.prog.Current())), humanize.Bytes(uint64(s.prog.Size())))
|
||||
}
|
||||
return stage
|
||||
}
|
||||
|
||||
func (m *Handler) handleUpdateVulnerabilityDatabase(e partybus.Event) []tea.Model {
|
||||
prog, err := parsers.ParseUpdateVulnerabilityDatabase(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
tsk := m.newTaskProgress(
|
||||
taskprogress.Title{
|
||||
Default: "Vulnerability DB",
|
||||
},
|
||||
taskprogress.WithStagedProgressable(prog), // ignore the static stage provided by the event
|
||||
taskprogress.WithStager(dbDownloadProgressStager{prog: prog}),
|
||||
)
|
||||
|
||||
tsk.HideStageOnSuccess = false
|
||||
|
||||
return []tea.Model{tsk}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/gkampitakis/go-snaps/snaps"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
)
|
||||
|
||||
func TestHandler_handleUpdateVulnerabilityDatabase(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
eventFn func(*testing.T) partybus.Event
|
||||
iterations int
|
||||
}{
|
||||
{
|
||||
name: "downloading DB",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
prog := &progress.Manual{}
|
||||
prog.SetTotal(100)
|
||||
prog.Set(50)
|
||||
|
||||
mon := struct {
|
||||
progress.Progressable
|
||||
progress.Stager
|
||||
}{
|
||||
Progressable: prog,
|
||||
Stager: &progress.Stage{
|
||||
Current: "current",
|
||||
},
|
||||
}
|
||||
|
||||
return partybus.Event{
|
||||
Type: event.UpdateVulnerabilityDatabase,
|
||||
Value: mon,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DB download complete",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
prog := &progress.Manual{}
|
||||
prog.SetTotal(100)
|
||||
prog.Set(100)
|
||||
prog.SetCompleted()
|
||||
|
||||
mon := struct {
|
||||
progress.Progressable
|
||||
progress.Stager
|
||||
}{
|
||||
Progressable: prog,
|
||||
Stager: &progress.Stage{
|
||||
Current: "current",
|
||||
},
|
||||
}
|
||||
|
||||
return partybus.Event{
|
||||
Type: event.UpdateVulnerabilityDatabase,
|
||||
Value: mon,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := tt.eventFn(t)
|
||||
handler := New(DefaultHandlerConfig())
|
||||
handler.WindowSize = tea.WindowSizeMsg{
|
||||
Width: 100,
|
||||
Height: 80,
|
||||
}
|
||||
|
||||
models := handler.Handle(e)
|
||||
require.Len(t, models, 1)
|
||||
model := models[0]
|
||||
|
||||
tsk, ok := model.(taskprogress.Model)
|
||||
require.True(t, ok)
|
||||
|
||||
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
|
||||
Time: time.Now(),
|
||||
Sequence: tsk.Sequence(),
|
||||
ID: tsk.ID(),
|
||||
})
|
||||
t.Log(got)
|
||||
snaps.MatchSnapshot(t, got)
|
||||
})
|
||||
}
|
||||
}
|
204
cmd/grype/cli/ui/handle_vulnerability_scanning_started.go
Normal file
204
cmd/grype/cli/ui/handle_vulnerability_scanning_started.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
branch = "├──"
|
||||
end = "└──"
|
||||
)
|
||||
|
||||
var _ progress.StagedProgressable = (*vulnerabilityScanningAdapter)(nil)
|
||||
|
||||
type vulnerabilityProgressTree struct {
|
||||
mon *monitor.Matching
|
||||
windowSize tea.WindowSizeMsg
|
||||
|
||||
countBySeverity map[vulnerability.Severity]int64
|
||||
unknownCount int64
|
||||
fixedCount int64
|
||||
severities []vulnerability.Severity
|
||||
|
||||
id uint32
|
||||
sequence int
|
||||
|
||||
updateDuration time.Duration
|
||||
textStyle lipgloss.Style
|
||||
}
|
||||
|
||||
func newVulnerabilityProgressTree(monitor *monitor.Matching, textStyle lipgloss.Style) vulnerabilityProgressTree {
|
||||
allSeverities := vulnerability.AllSeverities()
|
||||
sort.Sort(sort.Reverse(vulnerability.Severities(allSeverities)))
|
||||
|
||||
return vulnerabilityProgressTree{
|
||||
mon: monitor,
|
||||
countBySeverity: make(map[vulnerability.Severity]int64),
|
||||
severities: allSeverities,
|
||||
textStyle: textStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// vulnerabilityProgressTreeTickMsg indicates that the timer has ticked and we should render a frame.
|
||||
type vulnerabilityProgressTreeTickMsg struct {
|
||||
Time time.Time
|
||||
Sequence int
|
||||
ID uint32
|
||||
}
|
||||
|
||||
type vulnerabilityScanningAdapter struct {
|
||||
mon *monitor.Matching
|
||||
}
|
||||
|
||||
func (p vulnerabilityScanningAdapter) Current() int64 {
|
||||
return p.mon.VulnerabilitiesDiscovered.Current()
|
||||
}
|
||||
|
||||
func (p vulnerabilityScanningAdapter) Error() error {
|
||||
return p.mon.VulnerabilitiesDiscovered.Error()
|
||||
}
|
||||
|
||||
func (p vulnerabilityScanningAdapter) Size() int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (p vulnerabilityScanningAdapter) Stage() string {
|
||||
return fmt.Sprintf("%d vulnerabilities", p.mon.VulnerabilitiesDiscovered.Current())
|
||||
}
|
||||
|
||||
func (m *Handler) handleVulnerabilityScanningStarted(e partybus.Event) []tea.Model {
|
||||
mon, err := parsers.ParseVulnerabilityScanningStarted(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("unable to parse event")
|
||||
return nil
|
||||
}
|
||||
|
||||
tsk := m.newTaskProgress(
|
||||
taskprogress.Title{
|
||||
Default: "Scan for vulnerabilities",
|
||||
Running: "Scanning for vulnerabilities",
|
||||
Success: "Scanned for vulnerabilities",
|
||||
},
|
||||
taskprogress.WithStagedProgressable(vulnerabilityScanningAdapter{mon: mon}),
|
||||
)
|
||||
|
||||
tsk.HideStageOnSuccess = false
|
||||
|
||||
textStyle := tsk.HintStyle
|
||||
|
||||
return []tea.Model{
|
||||
tsk,
|
||||
newVulnerabilityProgressTree(mon, textStyle),
|
||||
}
|
||||
}
|
||||
|
||||
func (l vulnerabilityProgressTree) Init() tea.Cmd {
|
||||
// this is the periodic update of state information
|
||||
return func() tea.Msg {
|
||||
return vulnerabilityProgressTreeTickMsg{
|
||||
// The time at which the tick occurred.
|
||||
Time: time.Now(),
|
||||
|
||||
// The ID of the log frame that this message belongs to. This can be
|
||||
// helpful when routing messages, however bear in mind that log frames
|
||||
// will ignore messages that don't contain ID by default.
|
||||
ID: l.id,
|
||||
|
||||
Sequence: l.sequence,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l vulnerabilityProgressTree) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
l.windowSize = msg
|
||||
return l, nil
|
||||
|
||||
case vulnerabilityProgressTreeTickMsg:
|
||||
// update the model
|
||||
l.fixedCount = l.mon.Fixed.Current()
|
||||
l.unknownCount = l.mon.BySeverity[vulnerability.UnknownSeverity].Current()
|
||||
for _, sev := range l.severities {
|
||||
l.countBySeverity[sev] = l.mon.BySeverity[sev].Current()
|
||||
}
|
||||
|
||||
// kick off the next tick
|
||||
tickCmd := l.handleTick(msg)
|
||||
|
||||
return l, tickCmd
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l vulnerabilityProgressTree) View() string {
|
||||
sb := strings.Builder{}
|
||||
|
||||
for idx, sev := range l.severities {
|
||||
count := l.countBySeverity[sev]
|
||||
sb.WriteString(fmt.Sprintf("%d %s", count, sev))
|
||||
if idx < len(l.severities)-1 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if l.unknownCount > 0 {
|
||||
unknownStr := fmt.Sprintf(" (%d unknown)", l.unknownCount)
|
||||
sb.WriteString(unknownStr)
|
||||
}
|
||||
|
||||
status := sb.String()
|
||||
sb.Reset()
|
||||
|
||||
sevStr := l.textStyle.Render(fmt.Sprintf(" %s %s", branch, status))
|
||||
fixedStr := l.textStyle.Render(fmt.Sprintf(" %s %d fixed", end, l.fixedCount))
|
||||
|
||||
sb.WriteString(sevStr)
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(fixedStr)
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (l vulnerabilityProgressTree) queueNextTick() tea.Cmd {
|
||||
return tea.Tick(l.updateDuration, func(t time.Time) tea.Msg {
|
||||
return vulnerabilityProgressTreeTickMsg{
|
||||
Time: t,
|
||||
ID: l.id,
|
||||
Sequence: l.sequence,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (l *vulnerabilityProgressTree) handleTick(msg vulnerabilityProgressTreeTickMsg) tea.Cmd {
|
||||
// If an ID is set, and the ID doesn't belong to this log frame, reject the message.
|
||||
if msg.ID > 0 && msg.ID != l.id {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If a sequence is set, and it's not the one we expect, reject the message.
|
||||
// This prevents the log frame from receiving too many messages and
|
||||
// thus updating too frequently.
|
||||
if msg.Sequence > 0 && msg.Sequence != l.sequence {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.sequence++
|
||||
|
||||
// note: even if the log is completed we should still respond to stage changes and window size events
|
||||
return l.queueNextTick()
|
||||
}
|
145
cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go
Normal file
145
cmd/grype/cli/ui/handle_vulnerability_scanning_started_test.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/gkampitakis/go-snaps/snaps"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
)
|
||||
|
||||
func TestHandler_handleVulnerabilityScanningStarted(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
eventFn func(*testing.T) partybus.Event
|
||||
iterations int
|
||||
}{
|
||||
{
|
||||
name: "vulnerability scanning in progress",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
return partybus.Event{
|
||||
Type: event.VulnerabilityScanningStarted,
|
||||
Value: getVulnerabilityMonitor(false),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vulnerability scanning complete",
|
||||
eventFn: func(t *testing.T) partybus.Event {
|
||||
return partybus.Event{
|
||||
Type: event.VulnerabilityScanningStarted,
|
||||
Value: getVulnerabilityMonitor(true),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := tt.eventFn(t)
|
||||
handler := New(DefaultHandlerConfig())
|
||||
handler.WindowSize = tea.WindowSizeMsg{
|
||||
Width: 100,
|
||||
Height: 80,
|
||||
}
|
||||
|
||||
models := handler.Handle(e)
|
||||
require.Len(t, models, 2)
|
||||
|
||||
t.Run("task line", func(t *testing.T) {
|
||||
tsk, ok := models[0].(taskprogress.Model)
|
||||
require.True(t, ok)
|
||||
|
||||
got := runModel(t, tsk, tt.iterations, taskprogress.TickMsg{
|
||||
Time: time.Now(),
|
||||
Sequence: tsk.Sequence(),
|
||||
ID: tsk.ID(),
|
||||
})
|
||||
t.Log(got)
|
||||
snaps.MatchSnapshot(t, got)
|
||||
})
|
||||
|
||||
t.Run("tree", func(t *testing.T) {
|
||||
log, ok := models[1].(vulnerabilityProgressTree)
|
||||
require.True(t, ok)
|
||||
got := runModel(t, log, tt.iterations, vulnerabilityProgressTreeTickMsg{
|
||||
Time: time.Now(),
|
||||
Sequence: log.sequence,
|
||||
ID: log.id,
|
||||
})
|
||||
t.Log(got)
|
||||
snaps.MatchSnapshot(t, got)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getVulnerabilityMonitor(completed bool) monitor.Matching {
|
||||
pkgs := &progress.Manual{}
|
||||
pkgs.SetTotal(-1)
|
||||
if completed {
|
||||
pkgs.Set(2000)
|
||||
pkgs.SetCompleted()
|
||||
} else {
|
||||
pkgs.Set(300)
|
||||
}
|
||||
|
||||
vulns := &progress.Manual{}
|
||||
vulns.SetTotal(-1)
|
||||
if completed {
|
||||
vulns.Set(25)
|
||||
vulns.SetCompleted()
|
||||
} else {
|
||||
vulns.Set(20)
|
||||
}
|
||||
|
||||
fixed := &progress.Manual{}
|
||||
fixed.SetTotal(-1)
|
||||
if completed {
|
||||
fixed.Set(35)
|
||||
fixed.SetCompleted()
|
||||
} else {
|
||||
fixed.Set(30)
|
||||
}
|
||||
|
||||
bySeverityWriter := map[vulnerability.Severity]*progress.Manual{
|
||||
vulnerability.CriticalSeverity: {},
|
||||
vulnerability.HighSeverity: {},
|
||||
vulnerability.MediumSeverity: {},
|
||||
vulnerability.LowSeverity: {},
|
||||
vulnerability.NegligibleSeverity: {},
|
||||
vulnerability.UnknownSeverity: {},
|
||||
}
|
||||
|
||||
allSeverities := vulnerability.AllSeverities()
|
||||
sort.Sort(sort.Reverse(vulnerability.Severities(allSeverities)))
|
||||
|
||||
var count int64 = 1
|
||||
for _, sev := range allSeverities {
|
||||
bySeverityWriter[sev].Add(count)
|
||||
count++
|
||||
}
|
||||
bySeverityWriter[vulnerability.UnknownSeverity].Add(count)
|
||||
|
||||
bySeverity := map[vulnerability.Severity]progress.Monitorable{}
|
||||
|
||||
for k, v := range bySeverityWriter {
|
||||
bySeverity[k] = v
|
||||
}
|
||||
|
||||
return monitor.Matching{
|
||||
PackagesProcessed: pkgs,
|
||||
VulnerabilitiesDiscovered: vulns,
|
||||
Fixed: fixed,
|
||||
BySeverity: bySeverity,
|
||||
}
|
||||
}
|
66
cmd/grype/cli/ui/handler.go
Normal file
66
cmd/grype/cli/ui/handler.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/bubbly"
|
||||
"github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
)
|
||||
|
||||
var _ interface {
|
||||
bubbly.EventHandler
|
||||
bubbly.MessageListener
|
||||
bubbly.HandleWaiter
|
||||
} = (*Handler)(nil)
|
||||
|
||||
type HandlerConfig struct {
|
||||
TitleWidth int
|
||||
AdjustDefaultTask func(taskprogress.Model) taskprogress.Model
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
WindowSize tea.WindowSizeMsg
|
||||
Running *sync.WaitGroup
|
||||
Config HandlerConfig
|
||||
|
||||
bubbly.EventHandler
|
||||
}
|
||||
|
||||
func DefaultHandlerConfig() HandlerConfig {
|
||||
return HandlerConfig{
|
||||
TitleWidth: 30,
|
||||
}
|
||||
}
|
||||
|
||||
func New(cfg HandlerConfig) *Handler {
|
||||
d := bubbly.NewEventDispatcher()
|
||||
|
||||
h := &Handler{
|
||||
EventHandler: d,
|
||||
Running: &sync.WaitGroup{},
|
||||
Config: cfg,
|
||||
}
|
||||
|
||||
// register all supported event types with the respective handler functions
|
||||
d.AddHandlers(map[partybus.EventType]bubbly.EventHandlerFn{
|
||||
event.UpdateVulnerabilityDatabase: h.handleUpdateVulnerabilityDatabase,
|
||||
event.VulnerabilityScanningStarted: h.handleVulnerabilityScanningStarted,
|
||||
event.DatabaseDiffingStarted: h.handleDatabaseDiffStarted,
|
||||
})
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (m *Handler) OnMessage(msg tea.Msg) {
|
||||
if msg, ok := msg.(tea.WindowSizeMsg); ok {
|
||||
m.WindowSize = msg
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Handler) Wait() {
|
||||
m.Running.Wait()
|
||||
}
|
19
cmd/grype/cli/ui/new_task_progress.go
Normal file
19
cmd/grype/cli/ui/new_task_progress.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ui
|
||||
|
||||
import "github.com/anchore/bubbly/bubbles/taskprogress"
|
||||
|
||||
func (m Handler) newTaskProgress(title taskprogress.Title, opts ...taskprogress.Option) taskprogress.Model {
|
||||
tsk := taskprogress.New(m.Running, opts...)
|
||||
|
||||
tsk.HideProgressOnSuccess = true
|
||||
tsk.HideStageOnSuccess = true
|
||||
tsk.WindowSize = m.WindowSize
|
||||
tsk.TitleWidth = m.Config.TitleWidth
|
||||
tsk.TitleOptions = title
|
||||
|
||||
if m.Config.AdjustDefaultTask != nil {
|
||||
tsk = m.Config.AdjustDefaultTask(tsk)
|
||||
}
|
||||
|
||||
return tsk
|
||||
}
|
69
cmd/grype/cli/ui/util_test.go
Normal file
69
cmd/grype/cli/ui/util_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func runModel(t testing.TB, m tea.Model, iterations int, message tea.Msg, wgs ...*sync.WaitGroup) string {
|
||||
t.Helper()
|
||||
if iterations == 0 {
|
||||
iterations = 1
|
||||
}
|
||||
m.Init()
|
||||
var cmd tea.Cmd = func() tea.Msg {
|
||||
return message
|
||||
}
|
||||
|
||||
for _, wg := range wgs {
|
||||
if wg != nil {
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; cmd != nil && i < iterations; i++ {
|
||||
msgs := flatten(cmd())
|
||||
var nextCmds []tea.Cmd
|
||||
var next tea.Cmd
|
||||
for _, msg := range msgs {
|
||||
t.Logf("Message: %+v %+v\n", reflect.TypeOf(msg), msg)
|
||||
m, next = m.Update(msg)
|
||||
nextCmds = append(nextCmds, next)
|
||||
}
|
||||
cmd = tea.Batch(nextCmds...)
|
||||
}
|
||||
return m.View()
|
||||
}
|
||||
|
||||
func flatten(p tea.Msg) (msgs []tea.Msg) {
|
||||
if reflect.TypeOf(p).Name() == "batchMsg" {
|
||||
partials := extractBatchMessages(p)
|
||||
for _, m := range partials {
|
||||
msgs = append(msgs, flatten(m)...)
|
||||
}
|
||||
} else {
|
||||
msgs = []tea.Msg{p}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
func extractBatchMessages(m tea.Msg) (ret []tea.Msg) {
|
||||
sliceMsgType := reflect.SliceOf(reflect.TypeOf(tea.Cmd(nil)))
|
||||
value := reflect.ValueOf(m) // note: this is technically unaddressable
|
||||
|
||||
// make our own instance that is addressable
|
||||
valueCopy := reflect.New(value.Type()).Elem()
|
||||
valueCopy.Set(value)
|
||||
|
||||
cmds := reflect.NewAt(sliceMsgType, unsafe.Pointer(valueCopy.UnsafeAddr())).Elem()
|
||||
for i := 0; i < cmds.Len(); i++ {
|
||||
item := cmds.Index(i)
|
||||
r := item.Call(nil)
|
||||
ret = append(ret, r[0].Interface().(tea.Msg))
|
||||
}
|
||||
return ret
|
||||
}
|
46
cmd/grype/internal/ui/__snapshots__/post_ui_event_writer_test.snap
Executable file
46
cmd/grype/internal/ui/__snapshots__/post_ui_event_writer_test.snap
Executable file
|
@ -0,0 +1,46 @@
|
|||
|
||||
[Test_postUIEventWriter_write/no_events/stdout - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/no_events/stderr - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/all_events/stdout - 1]
|
||||
|
||||
|
||||
<my --
|
||||
-
|
||||
-
|
||||
report 1!!>
|
||||
<report 2>
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/all_events/stderr - 1]
|
||||
|
||||
|
||||
<my notification 1!!
|
||||
...still notifying>
|
||||
|
||||
|
||||
<notification 2>
|
||||
<notification 3>
|
||||
|
||||
|
||||
<my app can be updated!!
|
||||
...to this version>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/quiet_only_shows_report/stdout - 1]
|
||||
<report 1>
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/quiet_only_shows_report/stderr - 1]
|
||||
|
||||
---
|
44
cmd/grype/internal/ui/no_ui.go
Normal file
44
cmd/grype/internal/ui/no_ui.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
)
|
||||
|
||||
var _ clio.UI = (*NoUI)(nil)
|
||||
|
||||
type NoUI struct {
|
||||
finalizeEvents []partybus.Event
|
||||
subscription partybus.Unsubscribable
|
||||
quiet bool
|
||||
}
|
||||
|
||||
func None(quiet bool) *NoUI {
|
||||
return &NoUI{
|
||||
quiet: quiet,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NoUI) Setup(subscription partybus.Unsubscribable) error {
|
||||
n.subscription = subscription
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoUI) Handle(e partybus.Event) error {
|
||||
switch e.Type {
|
||||
case event.CLIReport, event.CLINotification:
|
||||
// keep these for when the UI is terminated to show to the screen (or perform other events)
|
||||
n.finalizeEvents = append(n.finalizeEvents, e)
|
||||
case event.CLIExit:
|
||||
return n.subscription.Unsubscribe()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NoUI) Teardown(_ bool) error {
|
||||
return newPostUIEventWriter(os.Stdout, os.Stderr).write(n.quiet, n.finalizeEvents...)
|
||||
}
|
133
cmd/grype/internal/ui/post_ui_event_writer.go
Normal file
133
cmd/grype/internal/ui/post_ui_event_writer.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
type postUIEventWriter struct {
|
||||
handles []postUIHandle
|
||||
}
|
||||
|
||||
type postUIHandle struct {
|
||||
respectQuiet bool
|
||||
event partybus.EventType
|
||||
writer io.Writer
|
||||
dispatch eventWriter
|
||||
}
|
||||
|
||||
type eventWriter func(io.Writer, ...partybus.Event) error
|
||||
|
||||
func newPostUIEventWriter(stdout, stderr io.Writer) *postUIEventWriter {
|
||||
return &postUIEventWriter{
|
||||
handles: []postUIHandle{
|
||||
{
|
||||
event: event.CLIReport,
|
||||
respectQuiet: false,
|
||||
writer: stdout,
|
||||
dispatch: writeReports,
|
||||
},
|
||||
{
|
||||
event: event.CLINotification,
|
||||
respectQuiet: true,
|
||||
writer: stderr,
|
||||
dispatch: writeNotifications,
|
||||
},
|
||||
{
|
||||
event: event.CLIAppUpdateAvailable,
|
||||
respectQuiet: true,
|
||||
writer: stderr,
|
||||
dispatch: writeAppUpdate,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w postUIEventWriter) write(quiet bool, events ...partybus.Event) error {
|
||||
var errs error
|
||||
for _, h := range w.handles {
|
||||
if quiet && h.respectQuiet {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, e := range events {
|
||||
if e.Type != h.event {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := h.dispatch(h.writer, e); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func writeReports(writer io.Writer, events ...partybus.Event) error {
|
||||
var reports []string
|
||||
for _, e := range events {
|
||||
_, report, err := parsers.ParseCLIReport(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("failed to gather final report")
|
||||
continue
|
||||
}
|
||||
|
||||
// remove all whitespace padding from the end of the report
|
||||
reports = append(reports, strings.TrimRight(report, "\n ")+"\n")
|
||||
}
|
||||
|
||||
// prevent the double new-line at the end of the report
|
||||
report := strings.Join(reports, "\n")
|
||||
|
||||
if _, err := fmt.Fprint(writer, report); err != nil {
|
||||
return fmt.Errorf("failed to write final report to stdout: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeNotifications(writer io.Writer, events ...partybus.Event) error {
|
||||
// 13 = high intensity magenta (ANSI 16 bit code)
|
||||
style := lipgloss.NewStyle().Foreground(lipgloss.Color("13"))
|
||||
|
||||
for _, e := range events {
|
||||
_, notification, err := parsers.ParseCLINotification(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("failed to parse notification")
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(writer, style.Render(notification)); err != nil {
|
||||
// don't let this be fatal
|
||||
log.WithFields("error", err).Warn("failed to write final notifications")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAppUpdate(writer io.Writer, events ...partybus.Event) error {
|
||||
// 13 = high intensity magenta (ANSI 16 bit code) + italics
|
||||
style := lipgloss.NewStyle().Foreground(lipgloss.Color("13")).Italic(true)
|
||||
|
||||
for _, e := range events {
|
||||
notice, err := parsers.ParseCLIAppUpdateAvailable(e)
|
||||
if err != nil {
|
||||
log.WithFields("error", err).Warn("failed to parse app update notification")
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(writer, style.Render(notice)); err != nil {
|
||||
// don't let this be fatal
|
||||
log.WithFields("error", err).Warn("failed to write app update notification")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
95
cmd/grype/internal/ui/post_ui_event_writer_test.go
Normal file
95
cmd/grype/internal/ui/post_ui_event_writer_test.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/gkampitakis/go-snaps/snaps"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/grype/grype/event"
|
||||
)
|
||||
|
||||
func Test_postUIEventWriter_write(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
quiet bool
|
||||
events []partybus.Event
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
},
|
||||
{
|
||||
name: "all events",
|
||||
events: []partybus.Event{
|
||||
{
|
||||
Type: event.CLINotification,
|
||||
Value: "\n\n<my notification 1!!\n...still notifying>\n\n",
|
||||
},
|
||||
{
|
||||
Type: event.CLINotification,
|
||||
Value: "<notification 2>",
|
||||
},
|
||||
{
|
||||
Type: event.CLIAppUpdateAvailable,
|
||||
Value: "\n\n<my app can be updated!!\n...to this version>\n\n",
|
||||
},
|
||||
{
|
||||
Type: event.CLINotification,
|
||||
Value: "<notification 3>",
|
||||
},
|
||||
{
|
||||
Type: event.CLIReport,
|
||||
Value: "\n\n<my --\n-\n-\nreport 1!!>\n\n",
|
||||
},
|
||||
{
|
||||
Type: event.CLIReport,
|
||||
Value: "<report 2>",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "quiet only shows report",
|
||||
quiet: true,
|
||||
events: []partybus.Event{
|
||||
|
||||
{
|
||||
Type: event.CLINotification,
|
||||
Value: "<notification 1>",
|
||||
},
|
||||
{
|
||||
Type: event.CLIAppUpdateAvailable,
|
||||
Value: "<app update>",
|
||||
},
|
||||
{
|
||||
Type: event.CLIReport,
|
||||
Value: "<report 1>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
w := newPostUIEventWriter(stdout, stderr)
|
||||
|
||||
tt.wantErr(t, w.write(tt.quiet, tt.events...))
|
||||
|
||||
t.Run("stdout", func(t *testing.T) {
|
||||
snaps.MatchSnapshot(t, stdout.String())
|
||||
})
|
||||
|
||||
t.Run("stderr", func(t *testing.T) {
|
||||
snaps.MatchSnapshot(t, stderr.String())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,32 +1,39 @@
|
|||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// TODO: build tags to exclude options from windows
|
||||
"github.com/anchore/clio"
|
||||
grypeHandler "github.com/anchore/grype/cmd/grype/cli/ui"
|
||||
syftHandler "github.com/anchore/syft/cmd/syft/cli/ui"
|
||||
)
|
||||
|
||||
// Select is responsible for determining the specific UI function given select user option, the current platform
|
||||
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
|
||||
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
|
||||
// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of
|
||||
// the final SBOM report.
|
||||
func Select(verbose, quiet bool, reportWriter io.Writer) (uis []UI) {
|
||||
func Select(verbose, quiet bool) (uis []clio.UI) {
|
||||
isStdoutATty := term.IsTerminal(int(os.Stdout.Fd()))
|
||||
isStderrATty := term.IsTerminal(int(os.Stderr.Fd()))
|
||||
notATerminal := !isStderrATty && !isStdoutATty
|
||||
|
||||
switch {
|
||||
case verbose || quiet || notATerminal || !isStderrATty:
|
||||
uis = append(uis, NewLoggerUI(reportWriter))
|
||||
case runtime.GOOS == "windows" || verbose || quiet || notATerminal || !isStderrATty:
|
||||
uis = append(uis, None(quiet))
|
||||
default:
|
||||
uis = append(uis, NewEphemeralTerminalUI(reportWriter), NewLoggerUI(reportWriter))
|
||||
// TODO: it may make sense in the future to pass handler options into select
|
||||
|
||||
uis = append(uis,
|
||||
New(
|
||||
verbose, quiet,
|
||||
grypeHandler.New(grypeHandler.DefaultHandlerConfig()),
|
||||
syftHandler.New(syftHandler.DefaultHandlerConfig()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return uis
|
163
cmd/grype/internal/ui/ui.go
Normal file
163
cmd/grype/internal/ui/ui.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
"github.com/anchore/bubbly"
|
||||
"github.com/anchore/bubbly/bubbles/frame"
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/go-logger"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
var _ interface {
|
||||
tea.Model
|
||||
partybus.Responder
|
||||
clio.UI
|
||||
} = (*UI)(nil)
|
||||
|
||||
type UI struct {
|
||||
program *tea.Program
|
||||
running *sync.WaitGroup
|
||||
quiet bool
|
||||
subscription partybus.Unsubscribable
|
||||
finalizeEvents []partybus.Event
|
||||
|
||||
handler *bubbly.HandlerCollection
|
||||
frame tea.Model
|
||||
}
|
||||
|
||||
func New(_, quiet bool, hs ...bubbly.EventHandler) *UI {
|
||||
return &UI{
|
||||
handler: bubbly.NewHandlerCollection(hs...),
|
||||
frame: frame.New(),
|
||||
running: &sync.WaitGroup{},
|
||||
quiet: quiet,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *UI) Setup(subscription partybus.Unsubscribable) error {
|
||||
// we still want to collect log messages, however, we also the logger shouldn't write to the screen directly
|
||||
if logWrapper, ok := log.Get().(logger.Controller); ok {
|
||||
logWrapper.SetOutput(m.frame.(*frame.Frame).Footer())
|
||||
}
|
||||
|
||||
m.subscription = subscription
|
||||
m.program = tea.NewProgram(m, tea.WithOutput(os.Stderr), tea.WithInput(os.Stdin))
|
||||
m.running.Add(1)
|
||||
|
||||
go func() {
|
||||
defer m.running.Done()
|
||||
if _, err := m.program.Run(); err != nil {
|
||||
log.Errorf("unable to start UI: %+v", err)
|
||||
m.exit()
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UI) exit() {
|
||||
// stop the event loop
|
||||
bus.Exit()
|
||||
}
|
||||
|
||||
func (m *UI) Handle(e partybus.Event) error {
|
||||
if m.program != nil {
|
||||
m.program.Send(e)
|
||||
if e.Type == event.CLIExit {
|
||||
return m.subscription.Unsubscribe()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UI) Teardown(force bool) error {
|
||||
if !force {
|
||||
m.handler.Wait()
|
||||
m.program.Quit()
|
||||
} else {
|
||||
m.program.Kill()
|
||||
}
|
||||
|
||||
m.running.Wait()
|
||||
|
||||
// TODO: allow for writing out the full log output to the screen (only a partial log is shown currently)
|
||||
// this needs coordination to know what the last frame event is to change the state accordingly (which isn't possible now)
|
||||
|
||||
return newPostUIEventWriter(os.Stdout, os.Stderr).write(m.quiet, m.finalizeEvents...)
|
||||
}
|
||||
|
||||
// bubbletea.Model functions
|
||||
|
||||
func (m UI) Init() tea.Cmd {
|
||||
return m.frame.Init()
|
||||
}
|
||||
|
||||
func (m UI) RespondsTo() []partybus.EventType {
|
||||
return append([]partybus.EventType{
|
||||
event.CLIReport,
|
||||
event.CLINotification,
|
||||
event.CLIExit,
|
||||
event.CLIAppUpdateAvailable,
|
||||
}, m.handler.RespondsTo()...)
|
||||
}
|
||||
|
||||
func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// note: we need a pointer receiver such that the same instance of UI used in Teardown is referenced (to keep finalize events)
|
||||
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// allow for non-partybus UI updates (such as window size events). Note: these must not affect existing models,
|
||||
// that is the responsibility of the frame object on this UI object. The handler is a factory of models
|
||||
// which the frame is responsible for the lifecycle of. This update allows for injecting the initial state
|
||||
// of the world when creating those models.
|
||||
m.handler.OnMessage(msg)
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc", "ctrl+c":
|
||||
m.exit()
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
case partybus.Event:
|
||||
log.WithFields("component", "ui").Tracef("event: %q", msg.Type)
|
||||
|
||||
switch msg.Type {
|
||||
case event.CLIReport, event.CLINotification, event.CLIExit, event.CLIAppUpdateAvailable:
|
||||
// keep these for when the UI is terminated to show to the screen (or perform other events)
|
||||
m.finalizeEvents = append(m.finalizeEvents, msg)
|
||||
|
||||
// why not return tea.Quit here for exit events? because there may be UI components that still need the update-render loop.
|
||||
// for this reason we'll let the syft event loop call Teardown() which will explicitly wait for these components
|
||||
return m, nil
|
||||
}
|
||||
|
||||
for _, newModel := range m.handler.Handle(msg) {
|
||||
if newModel == nil {
|
||||
continue
|
||||
}
|
||||
cmds = append(cmds, newModel.Init())
|
||||
m.frame.(*frame.Frame).AppendModel(newModel)
|
||||
}
|
||||
// intentionally fallthrough to update the frame model
|
||||
}
|
||||
|
||||
frameModel, cmd := m.frame.Update(msg)
|
||||
m.frame = frameModel
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m UI) View() string {
|
||||
return m.frame.View()
|
||||
}
|
9
cmd/grype/main.go
Normal file
9
cmd/grype/main.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/anchore/grype/cmd/grype/cli/legacy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
legacy.Execute()
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func reportWriter() (io.Writer, func() error, error) {
|
||||
nop := func() error { return nil }
|
||||
path := strings.TrimSpace(appConfig.File)
|
||||
|
||||
switch len(path) {
|
||||
case 0:
|
||||
return os.Stdout, nop, nil
|
||||
|
||||
default:
|
||||
reportFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
|
||||
if err != nil {
|
||||
return nil, nop, fmt.Errorf("unable to create report file: %w", err)
|
||||
}
|
||||
|
||||
return reportFile, func() error {
|
||||
if !appConfig.Quiet {
|
||||
fmt.Printf("Report written to %q\n", path)
|
||||
}
|
||||
|
||||
return reportFile.Close()
|
||||
}, nil
|
||||
}
|
||||
}
|
28
go.mod
28
go.mod
|
@ -43,7 +43,6 @@ require (
|
|||
github.com/stretchr/testify v1.8.4
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/term v0.10.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
@ -51,9 +50,14 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10
|
||||
github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe
|
||||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963
|
||||
github.com/anchore/syft v0.85.0
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.7.1
|
||||
github.com/gkampitakis/go-snaps v0.4.7
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b
|
||||
|
@ -73,14 +77,19 @@ require (
|
|||
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect
|
||||
github.com/acobaugh/osrelease v0.1.0 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 // indirect
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.180 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.16.1 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/containerd/containerd v1.7.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
@ -95,6 +104,8 @@ require (
|
|||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/github/go-spdx/v2 v2.1.2 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.2.4 // indirect
|
||||
github.com/gkampitakis/go-diff v1.3.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.4.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.7.0 // indirect
|
||||
|
@ -125,21 +136,30 @@ require (
|
|||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.19.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/pborman/indent v1.2.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
|
@ -148,6 +168,7 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/sassoftware/go-rpmutils v0.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.1 // indirect
|
||||
|
@ -159,6 +180,10 @@ require (
|
|||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/go-mtree v0.5.3 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
|
@ -167,6 +192,7 @@ require (
|
|||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/goleak v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
|
|
61
go.sum
61
go.sum
|
@ -227,6 +227,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10 h1:Wrqt9fd8ygEMyFtxncZU7RgW2qBu5CL1x876xIyUlPU=
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10/go.mod h1:Ger02eh5NpPm2IqkPAy396HU1KlK3BhOeCljDYXySSk=
|
||||
github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817 h1:YsE91GT81FQOAOKByAnJVeJY2q8AunJ1eNf1bDC/o8g=
|
||||
github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817/go.mod h1:H5f7dtqPQ6kbL0OHcLrc5N0zkIxLZPBL2oKUE03fLgA=
|
||||
github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097 h1:79jSyWO6WOV8HPEpOQBOr7WsC2DnBRpyl7zsdaahCcg=
|
||||
github.com/anchore/fangs v0.0.0-20230628163043-a51c5a39b097/go.mod h1:E3zNHEz7mizIFGJhuX+Ga7AbCmEN5TfzVDxmOfj7XZw=
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe h1:Df867YMmymdMG6z5IW8pR0/2CRpLIjYnaTXLp6j+s0k=
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
|
||||
|
@ -261,6 +267,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
|||
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI=
|
||||
github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
|
||||
github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
|
@ -280,6 +288,14 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
|
|||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
|
@ -300,6 +316,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg=
|
||||
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
|
@ -369,6 +387,12 @@ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9
|
|||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.1.2 h1:p+Tv0yMgcuO0/vnMe9Qh4tmUgYhI6AsLVlakZ/Sx+DM=
|
||||
github.com/github/go-spdx/v2 v2.1.2/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
|
||||
github.com/gkampitakis/ciinfo v0.2.4 h1:Ip1hf4K7ISRuVlDrheuhaeffg1VOhlyeFGaQ/vTxrtE=
|
||||
github.com/gkampitakis/ciinfo v0.2.4/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.4.7 h1:w1hOUdiKELxiTcRySQLmLuV3KW40mIRw3rzcYJGrb3I=
|
||||
github.com/gkampitakis/go-snaps v0.4.7/go.mod h1:8HW4KX3JKV8M0GSw69CvT+Jqhd1AlBPMPpBfjBI3bdY=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
|
@ -587,7 +611,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
|
@ -616,12 +639,15 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
|
@ -637,7 +663,6 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
|
|||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
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.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
|
@ -646,8 +671,11 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
|
@ -688,6 +716,14 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
|
@ -713,6 +749,8 @@ github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzB
|
|||
github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
|
||||
github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
|
@ -723,6 +761,7 @@ github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0
|
|||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -750,11 +789,13 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
|||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
|
@ -826,6 +867,16 @@ github.com/sylabs/squashfs v0.6.1 h1:4hgvHnD9JGlYWwT0bPYNt9zaz23mAV3Js+VEgQoRGYQ
|
|||
github.com/sylabs/squashfs v0.6.1/go.mod h1:ZwpbPCj0ocIvMy2br6KZmix6Gzh6fsGQcCnydMF+Kx8=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
|
@ -846,8 +897,6 @@ github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b h1:uWNQ0khA
|
|||
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b/go.mod h1:ewlIKbKV8l+jCj8rkdXIs361ocR5x3qGyoCSca47Gx8=
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 h1:lwgTsTy18nYqASnH58qyfRW/ldj7Gt2zzBvgYPzdA4s=
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb h1:Yz6VVOcLuWLAHYlJzTw7JKnWxdV/WXpug2X0quEzRnY=
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb/go.mod h1:nDi3BAC5nEbVbg+WSJDHLbjHv0ZToq8nMPA97XMxF3E=
|
||||
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/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
|
@ -864,6 +913,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
|
||||
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 h1:V+UsotZpAVvfj3X/LMoEytoLzSiP6Lg0F7wdVyu9gGg=
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1/go.mod h1:ly2RBz4mnz1yeuVbQA/VFwGjK3mnHGRj1JuoG336Bis=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
|
@ -885,7 +936,6 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
|
|||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -1155,7 +1205,6 @@ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
|||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
|
|
|
@ -9,14 +9,14 @@ serve: www/listing.json www/db.tar.gz server.crt
|
|||
grype-test-fail: clean-dbdir dbdir
|
||||
GRYPE_DB_CACHE_DIR=$(shell pwd)/dbdir \
|
||||
GRYPE_DB_UPDATE_URL=https://$(shell hostname).local/listing.json \
|
||||
go run ../../../../main.go -vv alpine:latest
|
||||
go run ../../../../cmd/grype -vv alpine:latest
|
||||
|
||||
.PHONY: grype-test-pass
|
||||
grype-test-pass: clean-dbdir dbdir
|
||||
GRYPE_DB_CA_CERT=$(shell pwd)/server.crt \
|
||||
GRYPE_DB_CACHE_DIR=$(shell pwd)/dbdir \
|
||||
GRYPE_DB_UPDATE_URL=https://$(shell hostname).local/listing.json \
|
||||
go run ../../../../main.go -vv alpine:latest
|
||||
go run ../../../../cmd/grype -vv alpine:latest
|
||||
|
||||
dbdir:
|
||||
mkdir -p dbdir
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"github.com/wagoodman/go-progress"
|
||||
|
||||
v3 "github.com/anchore/grype/grype/db/v3"
|
||||
diffEvents "github.com/anchore/grype/grype/differ/events"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
)
|
||||
|
||||
|
@ -32,18 +32,21 @@ type storeMetadata struct {
|
|||
}
|
||||
|
||||
// create manual progress bars for tracking the database diff's progress
|
||||
func trackDiff() (*progress.Manual, *progress.Manual) {
|
||||
rowsProcessed := progress.Manual{}
|
||||
differencesDiscovered := progress.Manual{}
|
||||
func trackDiff(total int64) (*progress.Manual, *progress.Manual, *progress.Stage) {
|
||||
stageProgress := &progress.Manual{}
|
||||
stageProgress.SetTotal(total)
|
||||
differencesDiscovered := &progress.Manual{}
|
||||
stager := &progress.Stage{}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.DatabaseDiffingStarted,
|
||||
Value: diffEvents.Monitor{
|
||||
RowsProcessed: progress.Monitorable(&rowsProcessed),
|
||||
DifferencesDiscovered: progress.Monitorable(&differencesDiscovered),
|
||||
Value: monitor.DBDiff{
|
||||
Stager: stager,
|
||||
StageProgress: progress.Progressable(stageProgress),
|
||||
DifferencesDiscovered: progress.Monitorable(differencesDiscovered),
|
||||
},
|
||||
})
|
||||
return &rowsProcessed, &differencesDiscovered
|
||||
return stageProgress, differencesDiscovered, stager
|
||||
}
|
||||
|
||||
// creates a map from an unpackaged key to a list of all packages associated with it
|
||||
|
|
|
@ -249,37 +249,45 @@ func (s *store) GetAllVulnerabilityMetadata() (*[]v3.VulnerabilityMetadata, erro
|
|||
|
||||
// DiffStore creates a diff between the current sql database and the given store
|
||||
func (s *store) DiffStore(targetStore v3.StoreReader) (*[]v3.Diff, error) {
|
||||
rowsProgress, diffItems := trackDiff()
|
||||
// 7 stages, one for each step of the diff process (stages)
|
||||
rowsProgress, diffItems, stager := trackDiff(7)
|
||||
|
||||
stager.Current = "reading target vulnerabilities"
|
||||
targetVulns, err := targetStore.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "reading base vulnerabilities"
|
||||
baseVulns, err := s.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "preparing"
|
||||
baseVulnPkgMap := buildVulnerabilityPkgsMap(baseVulns)
|
||||
targetVulnPkgMap := buildVulnerabilityPkgsMap(targetVulns)
|
||||
|
||||
stager.Current = "comparing vulnerabilities"
|
||||
allDiffsMap := diffVulnerabilities(baseVulns, targetVulns, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
|
||||
stager.Current = "reading base metadata"
|
||||
baseMetadata, err := s.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "reading target metadata"
|
||||
targetMetadata, err := targetStore.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "comparing metadata"
|
||||
metaDiffsMap := diffVulnerabilityMetadata(baseMetadata, targetMetadata, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
for k, diff := range *metaDiffsMap {
|
||||
(*allDiffsMap)[k] = diff
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"github.com/wagoodman/go-progress"
|
||||
|
||||
v4 "github.com/anchore/grype/grype/db/v4"
|
||||
diffEvents "github.com/anchore/grype/grype/differ/events"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
)
|
||||
|
||||
|
@ -32,18 +32,21 @@ type storeMetadata struct {
|
|||
}
|
||||
|
||||
// create manual progress bars for tracking the database diff's progress
|
||||
func trackDiff() (*progress.Manual, *progress.Manual) {
|
||||
rowsProcessed := progress.Manual{}
|
||||
differencesDiscovered := progress.Manual{}
|
||||
func trackDiff(total int64) (*progress.Manual, *progress.Manual, *progress.Stage) {
|
||||
stageProgress := &progress.Manual{}
|
||||
stageProgress.SetTotal(total)
|
||||
differencesDiscovered := &progress.Manual{}
|
||||
stager := &progress.Stage{}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.DatabaseDiffingStarted,
|
||||
Value: diffEvents.Monitor{
|
||||
RowsProcessed: progress.Monitorable(&rowsProcessed),
|
||||
DifferencesDiscovered: progress.Monitorable(&differencesDiscovered),
|
||||
Value: monitor.DBDiff{
|
||||
Stager: stager,
|
||||
StageProgress: progress.Progressable(stageProgress),
|
||||
DifferencesDiscovered: progress.Monitorable(differencesDiscovered),
|
||||
},
|
||||
})
|
||||
return &rowsProcessed, &differencesDiscovered
|
||||
return stageProgress, differencesDiscovered, stager
|
||||
}
|
||||
|
||||
// creates a map from an unpackaged key to a list of all packages associated with it
|
||||
|
|
|
@ -307,37 +307,45 @@ func (s *store) GetAllVulnerabilityMetadata() (*[]v4.VulnerabilityMetadata, erro
|
|||
|
||||
// DiffStore creates a diff between the current sql database and the given store
|
||||
func (s *store) DiffStore(targetStore v4.StoreReader) (*[]v4.Diff, error) {
|
||||
rowsProgress, diffItems := trackDiff()
|
||||
// 7 stages, one for each step of the diff process (stages)
|
||||
rowsProgress, diffItems, stager := trackDiff(7)
|
||||
|
||||
stager.Current = "reading target vulnerabilities"
|
||||
targetVulns, err := targetStore.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "reading base vulnerabilities"
|
||||
baseVulns, err := s.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "preparing"
|
||||
baseVulnPkgMap := buildVulnerabilityPkgsMap(baseVulns)
|
||||
targetVulnPkgMap := buildVulnerabilityPkgsMap(targetVulns)
|
||||
|
||||
stager.Current = "comparing vulnerabilities"
|
||||
allDiffsMap := diffVulnerabilities(baseVulns, targetVulns, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
|
||||
stager.Current = "reading base metadata"
|
||||
baseMetadata, err := s.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "reading target metadata"
|
||||
targetMetadata, err := targetStore.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "comparing metadata"
|
||||
metaDiffsMap := diffVulnerabilityMetadata(baseMetadata, targetMetadata, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
for k, diff := range *metaDiffsMap {
|
||||
(*allDiffsMap)[k] = diff
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"github.com/wagoodman/go-progress"
|
||||
|
||||
v5 "github.com/anchore/grype/grype/db/v5"
|
||||
diffEvents "github.com/anchore/grype/grype/differ/events"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/internal/bus"
|
||||
)
|
||||
|
||||
|
@ -32,18 +32,21 @@ type storeMetadata struct {
|
|||
}
|
||||
|
||||
// create manual progress bars for tracking the database diff's progress
|
||||
func trackDiff() (*progress.Manual, *progress.Manual) {
|
||||
rowsProcessed := progress.Manual{}
|
||||
differencesDiscovered := progress.Manual{}
|
||||
func trackDiff(total int64) (*progress.Manual, *progress.Manual, *progress.Stage) {
|
||||
stageProgress := &progress.Manual{}
|
||||
stageProgress.SetTotal(total)
|
||||
differencesDiscovered := &progress.Manual{}
|
||||
stager := &progress.Stage{}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.DatabaseDiffingStarted,
|
||||
Value: diffEvents.Monitor{
|
||||
RowsProcessed: progress.Monitorable(&rowsProcessed),
|
||||
DifferencesDiscovered: progress.Monitorable(&differencesDiscovered),
|
||||
Value: monitor.DBDiff{
|
||||
Stager: stager,
|
||||
StageProgress: progress.Progressable(stageProgress),
|
||||
DifferencesDiscovered: progress.Monitorable(differencesDiscovered),
|
||||
},
|
||||
})
|
||||
return &rowsProcessed, &differencesDiscovered
|
||||
return stageProgress, differencesDiscovered, stager
|
||||
}
|
||||
|
||||
// creates a map from an unpackaged key to a list of all packages associated with it
|
||||
|
|
|
@ -325,37 +325,45 @@ func (s *store) GetAllVulnerabilityMetadata() (*[]v5.VulnerabilityMetadata, erro
|
|||
|
||||
// DiffStore creates a diff between the current sql database and the given store
|
||||
func (s *store) DiffStore(targetStore v5.StoreReader) (*[]v5.Diff, error) {
|
||||
rowsProgress, diffItems := trackDiff()
|
||||
// 7 stages, one for each step of the diff process (stages)
|
||||
rowsProgress, diffItems, stager := trackDiff(7)
|
||||
|
||||
stager.Current = "reading target vulnerabilities"
|
||||
targetVulns, err := targetStore.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "reading base vulnerabilities"
|
||||
baseVulns, err := s.GetAllVulnerabilities()
|
||||
rowsProgress.Increment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stager.Current = "preparing"
|
||||
baseVulnPkgMap := buildVulnerabilityPkgsMap(baseVulns)
|
||||
targetVulnPkgMap := buildVulnerabilityPkgsMap(targetVulns)
|
||||
|
||||
stager.Current = "comparing vulnerabilities"
|
||||
allDiffsMap := diffVulnerabilities(baseVulns, targetVulns, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
|
||||
stager.Current = "reading base metadata"
|
||||
baseMetadata, err := s.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "reading target metadata"
|
||||
targetMetadata, err := targetStore.GetAllVulnerabilityMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsProgress.Increment()
|
||||
|
||||
stager.Current = "comparing metadata"
|
||||
metaDiffsMap := diffVulnerabilityMetadata(baseMetadata, targetMetadata, baseVulnPkgMap, targetVulnPkgMap, diffItems)
|
||||
for k, diff := range *metaDiffsMap {
|
||||
(*allDiffsMap)[k] = diff
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package events
|
||||
|
||||
import "github.com/wagoodman/go-progress"
|
||||
|
||||
type Monitor struct {
|
||||
RowsProcessed progress.Monitorable
|
||||
DifferencesDiscovered progress.Monitorable
|
||||
}
|
|
@ -10,9 +10,11 @@ const (
|
|||
typePrefix = internal.ApplicationName
|
||||
cliTypePrefix = typePrefix + "-cli"
|
||||
|
||||
UpdateVulnerabilityDatabase partybus.EventType = "grype-update-vulnerability-database"
|
||||
VulnerabilityScanningStarted partybus.EventType = "grype-vulnerability-scanning-started"
|
||||
DatabaseDiffingStarted partybus.EventType = "grype-database-diffing-started"
|
||||
// Events from the grype library
|
||||
|
||||
UpdateVulnerabilityDatabase partybus.EventType = typePrefix + "-update-vulnerability-database"
|
||||
VulnerabilityScanningStarted partybus.EventType = typePrefix + "-vulnerability-scanning-started"
|
||||
DatabaseDiffingStarted partybus.EventType = typePrefix + "-database-diffing-started"
|
||||
|
||||
// Events exclusively for the CLI
|
||||
|
||||
|
@ -22,6 +24,9 @@ const (
|
|||
// CLIReport is a partybus event that occurs when an analysis result is ready for final presentation to stdout
|
||||
CLIReport partybus.EventType = cliTypePrefix + "-report"
|
||||
|
||||
// CLINotification is a partybus event that occurs when auxiliary information is ready for presentation to stderr
|
||||
CLINotification partybus.EventType = cliTypePrefix + "-notification"
|
||||
|
||||
// CLIExit is a partybus event that occurs when an analysis result is ready for final presentation
|
||||
CLIExit partybus.EventType = cliTypePrefix + "-exit-event"
|
||||
)
|
||||
|
|
9
grype/event/monitor/db_diff.go
Normal file
9
grype/event/monitor/db_diff.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package monitor
|
||||
|
||||
import "github.com/wagoodman/go-progress"
|
||||
|
||||
type DBDiff struct {
|
||||
Stager progress.Stager
|
||||
StageProgress progress.Progressable
|
||||
DifferencesDiscovered progress.Monitorable
|
||||
}
|
14
grype/event/monitor/matching.go
Normal file
14
grype/event/monitor/matching.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package monitor
|
||||
|
||||
import (
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
)
|
||||
|
||||
type Matching struct {
|
||||
PackagesProcessed progress.Monitorable
|
||||
VulnerabilitiesDiscovered progress.Monitorable
|
||||
Fixed progress.Monitorable
|
||||
BySeverity map[vulnerability.Severity]progress.Monitorable
|
||||
}
|
|
@ -6,9 +6,8 @@ import (
|
|||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
diffEvents "github.com/anchore/grype/grype/differ/events"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
)
|
||||
|
||||
type ErrBadPayload struct {
|
||||
|
@ -49,30 +48,30 @@ func ParseUpdateVulnerabilityDatabase(e partybus.Event) (progress.StagedProgress
|
|||
return prog, nil
|
||||
}
|
||||
|
||||
func ParseVulnerabilityScanningStarted(e partybus.Event) (*matcher.Monitor, error) {
|
||||
func ParseVulnerabilityScanningStarted(e partybus.Event) (*monitor.Matching, error) {
|
||||
if err := checkEventType(e.Type, event.VulnerabilityScanningStarted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
monitor, ok := e.Value.(matcher.Monitor)
|
||||
mon, ok := e.Value.(monitor.Matching)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return &monitor, nil
|
||||
return &mon, nil
|
||||
}
|
||||
|
||||
func ParseDatabaseDiffingStarted(e partybus.Event) (*diffEvents.Monitor, error) {
|
||||
func ParseDatabaseDiffingStarted(e partybus.Event) (*monitor.DBDiff, error) {
|
||||
if err := checkEventType(e.Type, event.DatabaseDiffingStarted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
monitor, ok := e.Value.(diffEvents.Monitor)
|
||||
mon, ok := e.Value.(monitor.DBDiff)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return &monitor, nil
|
||||
return &mon, nil
|
||||
}
|
||||
|
||||
func ParseCLIAppUpdateAvailable(e partybus.Event) (string, error) {
|
||||
|
@ -106,3 +105,22 @@ func ParseCLIReport(e partybus.Event) (string, string, error) {
|
|||
|
||||
return context, report, nil
|
||||
}
|
||||
|
||||
func ParseCLINotification(e partybus.Event) (string, string, error) {
|
||||
if err := checkEventType(e.Type, event.CLINotification); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
context, ok := e.Source.(string)
|
||||
if !ok {
|
||||
// this is optional
|
||||
context = ""
|
||||
}
|
||||
|
||||
notification, ok := e.Value.(string)
|
||||
if !ok {
|
||||
return "", "", newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return context, notification, nil
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
func SetLogger(logger logger.Logger) {
|
||||
log.Log = logger
|
||||
func SetLogger(l logger.Logger) {
|
||||
log.Set(l)
|
||||
}
|
||||
|
||||
func SetBus(b *partybus.Bus) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
grypeDb "github.com/anchore/grype/grype/db/v5"
|
||||
"github.com/anchore/grype/grype/distro"
|
||||
"github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/monitor"
|
||||
"github.com/anchore/grype/grype/match"
|
||||
"github.com/anchore/grype/grype/matcher/apk"
|
||||
"github.com/anchore/grype/grype/matcher/dotnet"
|
||||
|
@ -28,28 +29,21 @@ import (
|
|||
syftPkg "github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
PackagesProcessed progress.Monitorable
|
||||
VulnerabilitiesDiscovered progress.Monitorable
|
||||
Fixed progress.Monitorable
|
||||
BySeverity map[vulnerability.Severity]progress.Monitorable
|
||||
}
|
||||
|
||||
type monitor struct {
|
||||
type monitorWriter struct {
|
||||
PackagesProcessed *progress.Manual
|
||||
VulnerabilitiesDiscovered *progress.Manual
|
||||
Fixed *progress.Manual
|
||||
BySeverity map[vulnerability.Severity]*progress.Manual
|
||||
}
|
||||
|
||||
func newMonitor() (monitor, Monitor) {
|
||||
func newMonitor() (monitorWriter, monitor.Matching) {
|
||||
manualBySev := make(map[vulnerability.Severity]*progress.Manual)
|
||||
for _, severity := range vulnerability.AllSeverities() {
|
||||
manualBySev[severity] = progress.NewManual(-1)
|
||||
}
|
||||
manualBySev[vulnerability.UnknownSeverity] = progress.NewManual(-1)
|
||||
|
||||
m := monitor{
|
||||
m := monitorWriter{
|
||||
PackagesProcessed: progress.NewManual(-1),
|
||||
VulnerabilitiesDiscovered: progress.NewManual(-1),
|
||||
Fixed: progress.NewManual(-1),
|
||||
|
@ -61,7 +55,7 @@ func newMonitor() (monitor, Monitor) {
|
|||
monitorableBySev[sev] = manual
|
||||
}
|
||||
|
||||
return m, Monitor{
|
||||
return m, monitor.Matching{
|
||||
PackagesProcessed: m.PackagesProcessed,
|
||||
VulnerabilitiesDiscovered: m.VulnerabilitiesDiscovered,
|
||||
Fixed: m.Fixed,
|
||||
|
@ -69,7 +63,7 @@ func newMonitor() (monitor, Monitor) {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *monitor) SetCompleted() {
|
||||
func (m *monitorWriter) SetCompleted() {
|
||||
m.PackagesProcessed.SetCompleted()
|
||||
m.VulnerabilitiesDiscovered.SetCompleted()
|
||||
m.Fixed.SetCompleted()
|
||||
|
@ -106,7 +100,7 @@ func NewDefaultMatchers(mc Config) []Matcher {
|
|||
}
|
||||
}
|
||||
|
||||
func trackMatcher() *monitor {
|
||||
func trackMatcher() *monitorWriter {
|
||||
writer, reader := newMonitor()
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
|
@ -200,7 +194,7 @@ func FindMatches(store interface {
|
|||
return res
|
||||
}
|
||||
|
||||
func logListSummary(vl *monitor) {
|
||||
func logListSummary(vl *monitorWriter) {
|
||||
log.Infof("found %d vulnerabilities for %d packages", vl.VulnerabilitiesDiscovered.Current(), vl.PackagesProcessed.Current())
|
||||
log.Debugf(" ├── fixed: %d", vl.Fixed.Current())
|
||||
log.Debugf(" └── matched: %d", vl.VulnerabilitiesDiscovered.Current())
|
||||
|
@ -234,7 +228,7 @@ func logIgnoredMatches(ignored []match.IgnoredMatch) {
|
|||
}
|
||||
}
|
||||
|
||||
func updateVulnerabilityList(list *monitor, matches []match.Match, metadataProvider vulnerability.MetadataProvider) {
|
||||
func updateVulnerabilityList(list *monitorWriter, matches []match.Match, metadataProvider vulnerability.MetadataProvider) {
|
||||
for _, m := range matches {
|
||||
metadata, err := metadataProvider.GetMetadata(m.Vulnerability.ID, m.Vulnerability.Namespace)
|
||||
if err != nil || metadata == nil {
|
||||
|
|
|
@ -3,17 +3,13 @@ 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 {
|
||||
if publisher != nil {
|
||||
publisher.Publish(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,3 +18,10 @@ func Report(report string) {
|
|||
Value: report,
|
||||
})
|
||||
}
|
||||
|
||||
func Notify(message string) {
|
||||
Publish(partybus.Event{
|
||||
Type: event.CLINotification,
|
||||
Value: message,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,65 +5,73 @@ import (
|
|||
"github.com/anchore/go-logger/adapter/discard"
|
||||
)
|
||||
|
||||
// Log is the singleton used to facilitate logging internally within syft
|
||||
var Log logger.Logger = discard.New()
|
||||
// log is the singleton used to facilitate logging internally within syft
|
||||
var log logger.Logger = discard.New()
|
||||
|
||||
func Set(l logger.Logger) {
|
||||
log = l
|
||||
}
|
||||
|
||||
func Get() logger.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
// Errorf takes a formatted template string and template arguments for the error logging level.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
Log.Errorf(format, args...)
|
||||
log.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Error logs the given arguments at the error logging level.
|
||||
func Error(args ...interface{}) {
|
||||
Log.Error(args...)
|
||||
log.Error(args...)
|
||||
}
|
||||
|
||||
// Warnf takes a formatted template string and template arguments for the warning logging level.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
Log.Warnf(format, args...)
|
||||
log.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warn logs the given arguments at the warning logging level.
|
||||
func Warn(args ...interface{}) {
|
||||
Log.Warn(args...)
|
||||
log.Warn(args...)
|
||||
}
|
||||
|
||||
// Infof takes a formatted template string and template arguments for the info logging level.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
Log.Infof(format, args...)
|
||||
log.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Info logs the given arguments at the info logging level.
|
||||
func Info(args ...interface{}) {
|
||||
Log.Info(args...)
|
||||
log.Info(args...)
|
||||
}
|
||||
|
||||
// Debugf takes a formatted template string and template arguments for the debug logging level.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
Log.Debugf(format, args...)
|
||||
log.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Debug logs the given arguments at the debug logging level.
|
||||
func Debug(args ...interface{}) {
|
||||
Log.Debug(args...)
|
||||
log.Debug(args...)
|
||||
}
|
||||
|
||||
// Tracef takes a formatted template string and template arguments for the trace logging level.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
Log.Tracef(format, args...)
|
||||
log.Tracef(format, args...)
|
||||
}
|
||||
|
||||
// Trace logs the given arguments at the trace logging level.
|
||||
func Trace(args ...interface{}) {
|
||||
Log.Trace(args...)
|
||||
log.Trace(args...)
|
||||
}
|
||||
|
||||
// WithFields returns a message logger with multiple key-value fields.
|
||||
func WithFields(fields ...interface{}) logger.MessageLogger {
|
||||
return Log.WithFields(fields...)
|
||||
return log.WithFields(fields...)
|
||||
}
|
||||
|
||||
// Nested returns a new logger with hard coded key-value pairs
|
||||
func Nested(fields ...interface{}) logger.Logger {
|
||||
return Log.Nested(fields...)
|
||||
return log.Nested(fields...)
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TODO: move me to a common module (used in multiple repos)
|
||||
|
||||
const SpinnerDotSet = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
||||
|
||||
type Spinner struct {
|
||||
index int
|
||||
charset []string
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewSpinner(charset string) Spinner {
|
||||
return Spinner{
|
||||
charset: strings.Split(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
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/jotframe/pkg/frame"
|
||||
|
||||
"github.com/anchore/go-logger"
|
||||
grypeEvent "github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
"github.com/anchore/grype/ui"
|
||||
)
|
||||
|
||||
// ephemeralTerminalUI provides an "ephemeral" terminal user interface to display the application state dynamically.
|
||||
// The terminal is placed into raw mode and the cursor is manipulated to allow for a dynamic, multi-line
|
||||
// UI (provided by the jotframe lib), for this reason all other application mechanisms that write to the screen
|
||||
// must be suppressed before starting (such as logs); since bytes in the device and in application memory combine to make
|
||||
// a shared state, bytes coming from elsewhere to the screen will disrupt this state.
|
||||
//
|
||||
// This UI is primarily driven off of events from the event bus, creating single-line terminal widgets to represent a
|
||||
// published element on the event bus, typically polling the element for the latest state. This allows for the UI to
|
||||
// control update frequency to the screen, provide "liveness" indications that are interpolated between bus events,
|
||||
// and overall loosely couple the bus events from screen interactions.
|
||||
//
|
||||
// By convention, all elements published on the bus should be treated as read-only, and publishers on the bus should
|
||||
// attempt to enforce this when possible by wrapping complex objects with interfaces to prescribe interactions. Also by
|
||||
// convention, each new event that the UI should respond to should be added either in this package as a handler function,
|
||||
// or in the shared ui package as a function on the main handler object. All handler functions should be completed
|
||||
// processing an event before the ETUI exits (coordinated with a sync.WaitGroup)
|
||||
type ephemeralTerminalUI struct {
|
||||
unsubscribe func() error
|
||||
handler *ui.Handler
|
||||
waitGroup *sync.WaitGroup
|
||||
frame *frame.Frame
|
||||
logBuffer *bytes.Buffer
|
||||
uiOutput *os.File
|
||||
reportOutput io.Writer
|
||||
reports []string
|
||||
}
|
||||
|
||||
// NewEphemeralTerminalUI writes all events to a TUI and writes the final report to the given writer.
|
||||
func NewEphemeralTerminalUI(reportWriter io.Writer) UI {
|
||||
return &ephemeralTerminalUI{
|
||||
handler: ui.NewHandler(),
|
||||
waitGroup: &sync.WaitGroup{},
|
||||
uiOutput: os.Stderr,
|
||||
reportOutput: reportWriter,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) Setup(unsubscribe func() error) error {
|
||||
h.unsubscribe = unsubscribe
|
||||
hideCursor(h.uiOutput)
|
||||
|
||||
// prep the logger to not clobber the screen from now on (logrus only)
|
||||
h.logBuffer = bytes.NewBufferString("")
|
||||
logController, ok := log.Log.(logger.Controller)
|
||||
if ok {
|
||||
logController.SetOutput(h.logBuffer)
|
||||
}
|
||||
|
||||
return h.openScreen()
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) Handle(event partybus.Event) error {
|
||||
ctx := context.Background()
|
||||
switch {
|
||||
case h.handler.RespondsTo(event):
|
||||
if err := h.handler.Handle(ctx, h.frame, event, h.waitGroup); err != nil {
|
||||
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||
}
|
||||
|
||||
case event.Type == grypeEvent.CLIAppUpdateAvailable:
|
||||
if err := handleCLIAppUpdateAvailable(ctx, h.frame, event, h.waitGroup); err != nil {
|
||||
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||
}
|
||||
|
||||
case event.Type == grypeEvent.CLIReport:
|
||||
_, report, err := parsers.ParseCLIReport(event)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||
break
|
||||
}
|
||||
h.reports = append(h.reports, report)
|
||||
|
||||
case event.Type == grypeEvent.CLIExit:
|
||||
h.closeScreen(false)
|
||||
|
||||
// this is the last expected event, stop listening to events
|
||||
return h.unsubscribe()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) openScreen() error {
|
||||
config := frame.Config{
|
||||
PositionPolicy: frame.PolicyFloatForward,
|
||||
// only report output to stderr, reserve report output for stdout
|
||||
Output: h.uiOutput,
|
||||
}
|
||||
|
||||
fr, err := frame.New(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the screen object: %w", err)
|
||||
}
|
||||
h.frame = fr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) closeScreen(force bool) {
|
||||
// we may have other background processes still displaying progress, wait for them to
|
||||
// finish before discontinuing dynamic content and showing the final report
|
||||
if !h.frame.IsClosed() {
|
||||
if !force {
|
||||
h.waitGroup.Wait()
|
||||
}
|
||||
h.frame.Close()
|
||||
// TODO: there is a race condition within frame.Close() that sometimes leads to an extra blank line being output
|
||||
frame.Close()
|
||||
|
||||
// only flush the log on close
|
||||
h.flushLog()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) flushLog() {
|
||||
// flush any errors to the screen before the report
|
||||
logController, ok := log.Log.(logger.Controller)
|
||||
if ok {
|
||||
fmt.Fprint(logController.GetOutput(), h.logBuffer.String())
|
||||
logController.SetOutput(h.uiOutput)
|
||||
} else {
|
||||
fmt.Fprint(h.uiOutput, h.logBuffer.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ephemeralTerminalUI) Teardown(force bool) error {
|
||||
h.closeScreen(force)
|
||||
showCursor(h.uiOutput)
|
||||
for _, report := range h.reports {
|
||||
if _, err := fmt.Fprintln(h.reportOutput, report); err != nil {
|
||||
return fmt.Errorf("failed to write report: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hideCursor(output io.Writer) {
|
||||
fmt.Fprint(output, "\x1b[?25l")
|
||||
}
|
||||
|
||||
func showCursor(output io.Writer) {
|
||||
fmt.Fprint(output, "\x1b[?25h")
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/jotframe/pkg/frame"
|
||||
|
||||
grypeEventParsers "github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal"
|
||||
"github.com/anchore/grype/internal/version"
|
||||
)
|
||||
|
||||
func handleCLIAppUpdateAvailable(_ context.Context, fr *frame.Frame, event partybus.Event, _ *sync.WaitGroup) error {
|
||||
newVersion, err := grypeEventParsers.ParseCLIAppUpdateAvailable(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad %s event: %w", event.Type, err)
|
||||
}
|
||||
|
||||
line, err := fr.Prepend()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := color.Magenta.Sprintf("New version of %s is available: %s (currently running: %s)", internal.ApplicationName, newVersion, version.FromBuild().Version)
|
||||
_, _ = io.WriteString(line, message)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
|
||||
grypeEvent "github.com/anchore/grype/grype/event"
|
||||
"github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/internal/log"
|
||||
)
|
||||
|
||||
type loggerUI struct {
|
||||
unsubscribe func() error
|
||||
reportOutput io.Writer
|
||||
reports []string
|
||||
}
|
||||
|
||||
// NewLoggerUI writes all events to the common application logger and writes the final report to the given writer.
|
||||
func NewLoggerUI(reportWriter io.Writer) UI {
|
||||
return &loggerUI{
|
||||
reportOutput: reportWriter,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loggerUI) Setup(unsubscribe func() error) error {
|
||||
l.unsubscribe = unsubscribe
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *loggerUI) Handle(event partybus.Event) error {
|
||||
switch event.Type {
|
||||
case grypeEvent.CLIReport:
|
||||
_, report, err := parsers.ParseCLIReport(event)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show %s event: %+v", event.Type, err)
|
||||
break
|
||||
}
|
||||
l.reports = append(l.reports, report)
|
||||
case grypeEvent.CLIExit:
|
||||
// this is the last expected event, stop listening to events
|
||||
return l.unsubscribe()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l loggerUI) Teardown(_ bool) error {
|
||||
for _, report := range l.reports {
|
||||
_, err := l.reportOutput.Write([]byte(report))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Select is responsible for determining the specific UI function given select user option, the current platform
|
||||
// config values, and environment status (such as a TTY being present). The first UI in the returned slice of UIs
|
||||
// is intended to be used and the UIs that follow are meant to be attempted only in a fallback posture when there
|
||||
// are environmental problems (e.g. cannot write to the terminal). A writer is provided to capture the output of
|
||||
// the final SBOM report.
|
||||
func Select(verbose, quiet bool, reportWriter io.Writer) (uis []UI) {
|
||||
return append(uis, NewLoggerUI(reportWriter))
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
type UI interface {
|
||||
Setup(unsubscribe func() error) error
|
||||
partybus.Handler
|
||||
Teardown(force bool) error
|
||||
}
|
9
main.go
9
main.go
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/anchore/grype/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -5,10 +5,10 @@ validate-schema: validate-schema-xml validate-schema-json
|
|||
|
||||
.PHONY: validate-schema-xml
|
||||
validate-schema-xml:
|
||||
go run ../../main.go -c ../../test/grype-test-config.yaml ubuntu:latest -vv -o cyclonedx-xml > bom.xml
|
||||
go run ../../cmd/grype -c ../../test/grype-test-config.yaml ubuntu:latest -vv -o cyclonedx-xml > bom.xml
|
||||
xmllint --noout --schema ./cyclonedx.xsd bom.xml
|
||||
|
||||
.PHONY: validate-schema-json
|
||||
validate-schema-json:
|
||||
go run ../../main.go -c ../../test/grype-test-config.yaml ubuntu:latest -vv -o cyclonedx-json > bom.json
|
||||
go run ../../cmd/grype -c ../../test/grype-test-config.yaml ubuntu:latest -vv -o cyclonedx-json > bom.json
|
||||
../../.tmp/yajsv -s cyclonedx.json bom.json
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
git+https://github.com/anchore/yardstick@b24e38cb80212c5fb58dc7a4fb83b7162df4a5b2
|
||||
git+https://github.com/anchore/yardstick@v0.6.0
|
||||
# ../../../yardstick
|
||||
tabulate==0.9.0
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"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"
|
||||
|
||||
grypeEventParsers "github.com/anchore/grype/grype/event/parsers"
|
||||
"github.com/anchore/grype/grype/matcher"
|
||||
"github.com/anchore/grype/grype/vulnerability"
|
||||
"github.com/anchore/grype/internal/ui/components"
|
||||
syftUI "github.com/anchore/syft/ui"
|
||||
)
|
||||
|
||||
const maxBarWidth = 50
|
||||
const statusSet = components.SpinnerDotSet // SpinnerCircleOutlineSet
|
||||
const completedStatus = "✔" // "●"
|
||||
const tileFormat = color.Bold
|
||||
|
||||
var (
|
||||
auxInfoFormat = color.HEX("#777777")
|
||||
statusTitleTemplate = fmt.Sprintf(" %%s %%-%ds ", syftUI.StatusTitleColumn)
|
||||
)
|
||||
|
||||
func startProcess() (format.Simple, *components.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 := components.NewSpinner(statusSet)
|
||||
|
||||
return formatter, &spinner
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
line, err := fr.Prepend()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
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":
|
||||
progStr += " "
|
||||
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("Vulnerability DB")
|
||||
auxInfo := auxInfoFormat.Sprintf("[%s]", prog.Stage())
|
||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
func scanningAndSummaryLines(fr *frame.Frame) (scanningLine, summaryLine, fixedLine *frame.Line, err error) {
|
||||
scanningLine, err = fr.Append()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
summaryLine, err = fr.Append()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
fixedLine, err = fr.Append()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return scanningLine, summaryLine, fixedLine, nil
|
||||
}
|
||||
|
||||
func assembleProgressMonitors(m *matcher.Monitor) []progress.Monitorable {
|
||||
ret := []progress.Monitorable{
|
||||
m.PackagesProcessed,
|
||||
m.VulnerabilitiesDiscovered,
|
||||
}
|
||||
|
||||
allSeverities := append([]vulnerability.Severity{vulnerability.UnknownSeverity}, vulnerability.AllSeverities()...)
|
||||
for _, sev := range allSeverities {
|
||||
ret = append(ret, m.BySeverity[sev])
|
||||
}
|
||||
|
||||
ret = append(ret, m.Fixed)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
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)
|
||||
}
|
||||
|
||||
scanningLine, summaryLine, fixLine, err := scanningAndSummaryLines(fr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
monitors := assembleProgressMonitors(monitor)
|
||||
|
||||
_, spinner := startProcess()
|
||||
stream := progress.StreamMonitors(ctx, monitors, 50*time.Millisecond)
|
||||
|
||||
title := tileFormat.Sprint("Scanning image...")
|
||||
branch := "├──"
|
||||
end := "└──"
|
||||
|
||||
fixTempl := "%d fixed"
|
||||
|
||||
formatFn := func(m *matcher.Monitor, complete bool) {
|
||||
var spin string
|
||||
if complete {
|
||||
spin = color.Green.Sprint(completedStatus)
|
||||
} else {
|
||||
spin = color.Magenta.Sprint(spinner.Next())
|
||||
}
|
||||
|
||||
auxInfo := auxInfoFormat.Sprintf("[%d vulnerabilities]", m.VulnerabilitiesDiscovered.Current())
|
||||
_, _ = io.WriteString(scanningLine, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||
|
||||
var unknownStr string
|
||||
unknown := m.BySeverity[vulnerability.UnknownSeverity].Current()
|
||||
if unknown > 0 {
|
||||
unknownStr = fmt.Sprintf(" (%d unknown)", unknown)
|
||||
}
|
||||
|
||||
allSeverities := vulnerability.AllSeverities()
|
||||
sort.Sort(sort.Reverse(vulnerability.Severities(allSeverities)))
|
||||
|
||||
var builder strings.Builder
|
||||
for idx, sev := range allSeverities {
|
||||
count := m.BySeverity[sev].Current()
|
||||
builder.WriteString(fmt.Sprintf("%d %s", count, sev))
|
||||
if idx < len(allSeverities)-1 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
}
|
||||
builder.WriteString(unknownStr)
|
||||
|
||||
status := builder.String()
|
||||
auxInfo2 := auxInfoFormat.Sprintf(" %s %s", branch, status)
|
||||
_, _ = io.WriteString(summaryLine, auxInfo2)
|
||||
|
||||
fixStatus := fmt.Sprintf(fixTempl, m.Fixed.Current())
|
||||
_, _ = io.WriteString(fixLine, auxInfoFormat.Sprintf(" %s %s", end, fixStatus))
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
formatFn(monitor, false)
|
||||
for range stream {
|
||||
formatFn(monitor, false)
|
||||
}
|
||||
formatFn(monitor, true)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Handler) DatabaseDiffingStartedHandler(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||
monitor, err := grypeEventParsers.ParseDatabaseDiffingStarted(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad %s event: %w", event.Type, err)
|
||||
}
|
||||
|
||||
line, err := fr.Append()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
_, spinner := startProcess()
|
||||
stream := progress.StreamMonitors(ctx, []progress.Monitorable{monitor.RowsProcessed, monitor.DifferencesDiscovered}, 50*time.Millisecond)
|
||||
title := tileFormat.Sprint("Diffing databases...")
|
||||
|
||||
formatFn := func(val int64) {
|
||||
spin := color.Magenta.Sprint(spinner.Next())
|
||||
auxInfo := auxInfoFormat.Sprintf("[differences %d]", val)
|
||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
formatFn(0)
|
||||
for p := range stream {
|
||||
formatFn(p[1])
|
||||
}
|
||||
|
||||
spin := color.Green.Sprint(completedStatus)
|
||||
title = tileFormat.Sprint("Diff Complete")
|
||||
auxInfo := auxInfoFormat.Sprintf("[%d differences]", monitor.DifferencesDiscovered.Current())
|
||||
_, _ = io.WriteString(line, fmt.Sprintf(statusTitleTemplate+"%s", spin, title, auxInfo))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/jotframe/pkg/frame"
|
||||
|
||||
grypeEvent "github.com/anchore/grype/grype/event"
|
||||
syftUI "github.com/anchore/syft/ui"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
syftHandler *syftUI.Handler
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
syftHandler: syftUI.NewHandler(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Handler) RespondsTo(event partybus.Event) bool {
|
||||
switch event.Type {
|
||||
case grypeEvent.VulnerabilityScanningStarted,
|
||||
grypeEvent.UpdateVulnerabilityDatabase,
|
||||
grypeEvent.DatabaseDiffingStarted:
|
||||
return true
|
||||
default:
|
||||
return r.syftHandler.RespondsTo(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Handler) Handle(ctx context.Context, fr *frame.Frame, event partybus.Event, wg *sync.WaitGroup) error {
|
||||
switch event.Type {
|
||||
case grypeEvent.VulnerabilityScanningStarted:
|
||||
return r.VulnerabilityScanningStartedHandler(ctx, fr, event, wg)
|
||||
case grypeEvent.UpdateVulnerabilityDatabase:
|
||||
return r.UpdateVulnerabilityDatabaseHandler(ctx, fr, event, wg)
|
||||
case grypeEvent.DatabaseDiffingStarted:
|
||||
return r.DatabaseDiffingStartedHandler(ctx, fr, event, wg)
|
||||
default:
|
||||
return r.syftHandler.Handle(ctx, fr, event, wg)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue