From 81edd50e1e3a9062ca12cda91e12cff37e3192a0 Mon Sep 17 00:00:00 2001 From: Shane Dell <32347414+shanedell@users.noreply.github.com> Date: Mon, 30 Oct 2023 09:57:46 -0400 Subject: [PATCH] Colorize severity in table output (#1284) * Colorize severity in table output - Create flag "--no-color" to allow disabling the color. By default its enabled. - When "--no-color" not specified highlight severity in its color: - Critical -> Bold Red - High -> Red - Medium -> Yellow - Low -> Green - Negligible -> Blue - Note: Golang doesn't have all colors available. Also, doesn't seem to be able use hex codes properly. - Add termenv to check if the terminal color profile supports colored output. If it doesn't default to noColor Closes #225 Signed-off-by: Shane Dell * fix: adopt EnvColorProfile to support NO_COLOR Signed-off-by: Christopher Phillips * fix linting and update snapshots Signed-off-by: Alex Goodman --------- Signed-off-by: Shane Dell Signed-off-by: Christopher Phillips Signed-off-by: Alex Goodman Co-authored-by: Christopher Phillips Co-authored-by: Alex Goodman --- go.mod | 17 ++++----- .../table/__snapshots__/presenter_test.snap | 12 +++++- grype/presenter/table/presenter.go | 37 ++++++++++++++++++- grype/presenter/table/presenter_test.go | 23 +++++++++--- .../snapshot/TestTablePresenter_Color.golden | 3 ++ 5 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 grype/presenter/table/test-fixtures/snapshot/TestTablePresenter_Color.golden diff --git a/go.mod b/go.mod index f18464cd..8e67cc54 100644 --- a/go.mod +++ b/go.mod @@ -37,35 +37,27 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d + github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 github.com/mholt/archiver/v3 v3.5.1 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/olekukonko/tablewriter v0.0.5 github.com/openvex/go-vex v0.2.5 - github.com/pkg/profile v1.7.0 // indirect + github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.10.0 github.com/spf13/cobra v1.7.0 - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.16.0 // indirect github.com/stretchr/testify v1.8.4 github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 github.com/x-cray/logrus-prefixed-formatter v0.5.2 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/term v0.13.0 // indirect gorm.io/gorm v1.25.5 - modernc.org/sqlite v1.26.0 // indirect -) - -require ( - github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 - github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 ) require ( @@ -196,6 +188,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/profile v1.7.0 // indirect 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 @@ -208,6 +201,8 @@ require ( github.com/spdx/tools-golang v0.5.3 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.16.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/sylabs/sif/v2 v2.11.5 // indirect github.com/sylabs/squashfs v0.6.1 // indirect @@ -236,6 +231,7 @@ require ( golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.13.0 // indirect @@ -253,4 +249,5 @@ require ( modernc.org/libc v1.24.1 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.6.0 // indirect + modernc.org/sqlite v1.26.0 // indirect ) diff --git a/grype/presenter/table/__snapshots__/presenter_test.snap b/grype/presenter/table/__snapshots__/presenter_test.snap index 11c2b0a8..e8a0e95a 100755 --- a/grype/presenter/table/__snapshots__/presenter_test.snap +++ b/grype/presenter/table/__snapshots__/presenter_test.snap @@ -1,5 +1,15 @@ -[TestTablePresenter - 1] +[TestTablePresenter/no_color - 1] +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical + +--- + +[TestTablePresenter/with_color - 1] +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low package-2 2.2.2 deb CVE-1999-0002 Critical diff --git a/grype/presenter/table/presenter.go b/grype/presenter/table/presenter.go index 4c0a4d3c..b5ba456d 100644 --- a/grype/presenter/table/presenter.go +++ b/grype/presenter/table/presenter.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "github.com/charmbracelet/lipgloss" "github.com/olekukonko/tablewriter" grypeDb "github.com/anchore/grype/grype/db/v5" @@ -27,6 +28,7 @@ type Presenter struct { packages []pkg.Package metadataProvider vulnerability.MetadataProvider showSuppressed bool + withColor bool } // NewPresenter is a *Presenter constructor @@ -37,6 +39,7 @@ func NewPresenter(pb models.PresenterConfig, showSuppressed bool) *Presenter { packages: pb.Packages, metadataProvider: pb.MetadataProvider, showSuppressed: showSuppressed, + withColor: supportsColor(), } } @@ -96,12 +99,24 @@ func (pres *Presenter) Present(output io.Writer) error { table.SetTablePadding(" ") table.SetNoWhiteSpace(true) - table.AppendBulk(rows) + if pres.withColor { + for _, row := range rows { + severityColor := getSeverityColor(row[len(row)-1]) + table.Rich(row, []tablewriter.Colors{{}, {}, {}, {}, {}, severityColor}) + } + } else { + table.AppendBulk(rows) + } + table.Render() return nil } +func supportsColor() bool { + return lipgloss.NewStyle().Foreground(lipgloss.Color("5")).Render("") != "" +} + func sortRows(rows [][]string) [][]string { // sort sort.SliceStable(rows, func(i, j int) bool { @@ -174,3 +189,23 @@ func createRow(m match.Match, metadataProvider vulnerability.MetadataProvider, s return []string{m.Package.Name, m.Package.Version, fixVersion, string(m.Package.Type), m.Vulnerability.ID, severity}, nil } + +func getSeverityColor(severity string) tablewriter.Colors { + severityFontType, severityColor := tablewriter.Normal, tablewriter.Normal + + switch strings.ToLower(severity) { + case "critical": + severityFontType = tablewriter.Bold + severityColor = tablewriter.FgRedColor + case "high": + severityColor = tablewriter.FgRedColor + case "medium": + severityColor = tablewriter.FgYellowColor + case "low": + severityColor = tablewriter.FgGreenColor + case "negligible": + severityColor = tablewriter.FgBlueColor + } + + return tablewriter.Colors{severityFontType, severityColor} +} diff --git a/grype/presenter/table/presenter_test.go b/grype/presenter/table/presenter_test.go index c2c7e789..f8f42885 100644 --- a/grype/presenter/table/presenter_test.go +++ b/grype/presenter/table/presenter_test.go @@ -83,12 +83,25 @@ func TestTablePresenter(t *testing.T) { pres := NewPresenter(pb, false) - // run presenter - err := pres.Present(&buffer) - require.NoError(t, err) + t.Run("no color", func(t *testing.T) { + pres.withColor = true - actual := buffer.String() - snaps.MatchSnapshot(t, actual) + err := pres.Present(&buffer) + require.NoError(t, err) + + actual := buffer.String() + snaps.MatchSnapshot(t, actual) + }) + + t.Run("with color", func(t *testing.T) { + pres.withColor = false + + err := pres.Present(&buffer) + require.NoError(t, err) + + actual := buffer.String() + snaps.MatchSnapshot(t, actual) + }) // TODO: add me back in when there is a JSON schema // validateAgainstDbSchema(t, string(actual)) diff --git a/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter_Color.golden b/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter_Color.golden new file mode 100644 index 00000000..2d492005 --- /dev/null +++ b/grype/presenter/table/test-fixtures/snapshot/TestTablePresenter_Color.golden @@ -0,0 +1,3 @@ +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical