Skip to content

Commit

Permalink
feat: implement secondary sorting for default json output (#1403)
Browse files Browse the repository at this point in the history
* feat: implement secondary sorting for default json output
---------
Signed-off-by: Christopher Phillips <[email protected]>
  • Loading branch information
spiffcs authored Jul 26, 2023
1 parent eb6c3b0 commit 05edf62
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 35 deletions.
13 changes: 13 additions & 0 deletions grype/presenter/json/presenter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"flag"
"regexp"
"sort"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -126,6 +127,18 @@ func TestEmptyJsonPresenter(t *testing.T) {

}

func TestPresenter_Present_NewDocumentSorted(t *testing.T) {
matches, packages, context, metadataProvider, appConfig, dbStatus := internal.GenerateAnalysis(t, internal.ImageSource)
doc, err := models.NewDocument(packages, context, matches, nil, metadataProvider, appConfig, dbStatus)
if err != nil {
t.Fatal(err)
}

if !sort.IsSorted(models.MatchSort(doc.Matches)) {
t.Errorf("expected matches to be sorted")
}
}

func redact(content []byte) []byte {
return timestampRegexp.ReplaceAll(content, []byte(`"timestamp":""`))
}
3 changes: 3 additions & 0 deletions grype/presenter/models/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package models

import (
"fmt"
"sort"
"time"

"github.com/anchore/grype/grype/match"
Expand Down Expand Up @@ -43,6 +44,8 @@ func NewDocument(packages []pkg.Package, context pkg.Context, matches match.Matc
findings = append(findings, *matchModel)
}

sort.Sort(MatchSort(findings))

var src *source
if context.Source != nil {
theSrc, err := newSource(*context.Source)
Expand Down
2 changes: 1 addition & 1 deletion grype/presenter/models/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestPackagesAreSorted(t *testing.T) {
actualVulnerabilities = append(actualVulnerabilities, m.Vulnerability.ID)
}

assert.Equal(t, []string{"CVE-1999-0001", "CVE-1999-0002", "CVE-1999-0003"}, actualVulnerabilities)
assert.Equal(t, []string{"CVE-1999-0003", "CVE-1999-0002", "CVE-1999-0001"}, actualVulnerabilities)
}

func TestTimestampValidFormat(t *testing.T) {
Expand Down
30 changes: 18 additions & 12 deletions grype/presenter/models/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,36 @@ func newMatch(m match.Match, p pkg.Package, metadataProvider vulnerability.Metad
}, nil
}

var _ sort.Interface = (*ByName)(nil)
var _ sort.Interface = (*MatchSort)(nil)

type ByName []Match
type MatchSort []Match

// Len is the number of elements in the collection.
func (m ByName) Len() int {
func (m MatchSort) Len() int {
return len(m)
}

// Less reports whether the element with index i should sort before the element with index j.
func (m ByName) Less(i, j int) bool {
if m[i].Artifact.Name == m[j].Artifact.Name {
if m[i].Vulnerability.ID == m[j].Vulnerability.ID {
if m[i].Artifact.Version == m[j].Artifact.Version {
return m[i].Artifact.Type < m[j].Artifact.Type
// sort should consistent across presenters: name, version, type, severity, vulnerability
func (m MatchSort) Less(i, j int) bool {
matchI := m[i]
matchJ := m[j]
if matchI.Artifact.Name == matchJ.Artifact.Name {
if matchI.Artifact.Version == matchJ.Artifact.Version {
if matchI.Artifact.Type == matchJ.Artifact.Type {
if SeverityScore(matchI.Vulnerability.Severity) == SeverityScore(matchJ.Vulnerability.Severity) {
return matchI.Vulnerability.ID > matchJ.Vulnerability.ID
}
return SeverityScore(matchI.Vulnerability.Severity) > SeverityScore(matchJ.Vulnerability.Severity)
}
return m[i].Artifact.Version < m[j].Artifact.Version
return matchI.Artifact.Type < matchJ.Artifact.Type
}
return m[i].Vulnerability.ID < m[j].Vulnerability.ID
return matchI.Artifact.Version < matchJ.Artifact.Version
}
return m[i].Artifact.Name < m[j].Artifact.Name
return matchI.Artifact.Name < matchJ.Artifact.Name
}

// Swap swaps the elements with indexes i and j.
func (m ByName) Swap(i, j int) {
func (m MatchSort) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
20 changes: 20 additions & 0 deletions grype/presenter/models/vulnerability_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,23 @@ func NewVulnerabilityMetadata(id, namespace string, metadata *vulnerability.Meta
Cvss: NewCVSS(metadata),
}
}

// returns severtiy score for presenter sorting purposes
func SeverityScore(severtiy string) int {
switch severtiy {
case "Unknown":
return 0
case "Negligible":
return 1
case "Low":
return 2
case "Medium":
return 3
case "High":
return 4
case "Critical":
return 5
default:
return 0
}
}
23 changes: 2 additions & 21 deletions grype/presenter/table/presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ func sortRows(rows [][]string) [][]string {
if rows[i][name] == rows[j][name] {
if rows[i][ver] == rows[j][ver] {
if rows[i][packageType] == rows[j][packageType] {
if sevScore(rows[i][sev]) == sevScore(rows[j][sev]) {
if models.SeverityScore(rows[i][sev]) == models.SeverityScore(rows[j][sev]) {
// we use > here to get the most recently filed vulnerabilities
// to show at the top of the severity
return rows[i][vuln] > rows[j][vuln]
}
return sevScore(rows[i][sev]) > sevScore(rows[j][sev])
return models.SeverityScore(rows[i][sev]) > models.SeverityScore(rows[j][sev])
}
return rows[i][packageType] < rows[j][packageType]
}
Expand All @@ -126,25 +126,6 @@ func sortRows(rows [][]string) [][]string {
return rows
}

func sevScore(sev string) int {
switch sev {
case "Unknown":
return 0
case "Negligible":
return 1
case "Low":
return 2
case "Medium":
return 3
case "High":
return 4
case "Critical":
return 5
default:
return 0
}
}

func removeDuplicateRows(items [][]string) [][]string {
seen := map[string][]string{}
var result [][]string
Expand Down
2 changes: 1 addition & 1 deletion grype/presenter/template/presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ var FuncMap = func() template.FuncMap {
return collection
}

sort.Sort(models.ByName(matches))
sort.Sort(models.MatchSort(matches))
return matches
}
return f
Expand Down

0 comments on commit 05edf62

Please sign in to comment.