mirror of
https://github.com/anchore/grype
synced 2024-11-10 14:44:12 +00:00
feat: implement secondary sorting for default json output (#1403)
* feat: implement secondary sorting for default json output --------- Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
eb6c3b0acd
commit
05edf62e62
7 changed files with 58 additions and 35 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"flag"
|
||||
"regexp"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -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":""`))
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/grype/grype/match"
|
||||
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue