Stabilize SPDX JSON output sorting (#1216)

This commit is contained in:
Keith Zantow 2022-09-19 15:31:00 -04:00 committed by GitHub
parent 0f99215b2c
commit c2005fad8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 216 additions and 11 deletions

View File

@ -5,7 +5,10 @@ import (
"regexp" "regexp"
"testing" "testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/formats/common/testutils" "github.com/anchore/syft/syft/formats/common/testutils"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
) )
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json encoders") var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json encoders")
@ -30,6 +33,36 @@ func TestSPDXJSONImageEncoder(t *testing.T) {
) )
} }
func TestSPDXRelationshipOrder(t *testing.T) {
testImage := "image-simple"
s := testutils.ImageInput(t, testImage, testutils.FromSnapshot())
addRelationships(&s)
testutils.AssertEncoderAgainstGoldenImageSnapshot(t,
Format(),
s,
testImage,
*updateSpdxJson,
spdxJsonRedactor,
)
}
func addRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted()
s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{}
for _, f := range []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"} {
meta := source.FileMetadata{}
coords := source.Coordinates{RealPath: f}
s.Artifacts.FileMetadata[coords] = meta
s.Relationships = append(s.Relationships, artifact.Relationship{
From: catalog[0],
To: coords,
Type: artifact.ContainsRelationship,
})
}
}
func spdxJsonRedactor(s []byte) []byte { func spdxJsonRedactor(s []byte) []byte {
// each SBOM reports the time it was generated, which is not useful during snapshot testing // each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted")) s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))

View File

@ -0,0 +1,151 @@
{
"SPDXID": "SPDXRef-DOCUMENT",
"name": "user-image-input",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2022-09-19T18:39:05.841331Z",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"licenseListVersion": "3.18"
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/user-image-input-6cf0595e-7d69-4990-aef5-8183b52023b9",
"packages": [
{
"SPDXID": "SPDXRef-2a46171f91c8d4bc",
"name": "package-1",
"licenseConcluded": "MIT",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "a-purl-1",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"hasFiles": [
"SPDXRef-5265a4dde3edbf7c",
"SPDXRef-839d99ee67d9d174",
"SPDXRef-9c2f7510199b17f6",
"SPDXRef-c641caa71518099f",
"SPDXRef-c6f5b29dca12661f",
"SPDXRef-f9e49132a4b96ccd"
],
"licenseDeclared": "MIT",
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
"versionInfo": "1.0.1"
},
{
"SPDXID": "SPDXRef-ae77680e9b1d087e",
"name": "package-2",
"licenseConcluded": "NONE",
"downloadLocation": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
"referenceType": "cpe23Type"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"licenseDeclared": "NONE",
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
"versionInfo": "2.0.1"
}
],
"files": [
{
"SPDXID": "SPDXRef-9c2f7510199b17f6",
"licenseConcluded": "NOASSERTION",
"fileName": "/a1/f6",
"fileTypes": [
"OTHER"
]
},
{
"SPDXID": "SPDXRef-c6f5b29dca12661f",
"licenseConcluded": "NOASSERTION",
"fileName": "/d1/f3",
"fileTypes": [
"OTHER"
]
},
{
"SPDXID": "SPDXRef-c641caa71518099f",
"licenseConcluded": "NOASSERTION",
"fileName": "/d2/f4",
"fileTypes": [
"OTHER"
]
},
{
"SPDXID": "SPDXRef-5265a4dde3edbf7c",
"licenseConcluded": "NOASSERTION",
"fileName": "/f1",
"fileTypes": [
"OTHER"
]
},
{
"SPDXID": "SPDXRef-f9e49132a4b96ccd",
"licenseConcluded": "NOASSERTION",
"fileName": "/f2",
"fileTypes": [
"OTHER"
]
},
{
"SPDXID": "SPDXRef-839d99ee67d9d174",
"licenseConcluded": "NOASSERTION",
"fileName": "/z1/f5",
"fileTypes": [
"OTHER"
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-5265a4dde3edbf7c"
},
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174"
},
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6"
},
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-c641caa71518099f"
},
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f"
},
{
"spdxElementId": "SPDXRef-2a46171f91c8d4bc",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd"
}
]
}

View File

@ -22,6 +22,8 @@ import (
func toFormatModel(s sbom.SBOM) *model.Document { func toFormatModel(s sbom.SBOM) *model.Document {
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source) name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)
relationships := s.RelationshipsSorted()
return &model.Document{ return &model.Document{
Element: model.Element{ Element: model.Element{
SPDXID: model.ElementID("DOCUMENT").String(), SPDXID: model.ElementID("DOCUMENT").String(),
@ -39,9 +41,9 @@ func toFormatModel(s sbom.SBOM) *model.Document {
}, },
DataLicense: "CC0-1.0", DataLicense: "CC0-1.0",
DocumentNamespace: namespace, DocumentNamespace: namespace,
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships), Packages: toPackages(s.Artifacts.PackageCatalog, relationships),
Files: toFiles(s), Files: toFiles(s),
Relationships: toRelationships(s.Relationships), Relationships: toRelationships(relationships),
} }
} }
@ -113,8 +115,8 @@ func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationsh
} }
from := model.ElementID(relationship.From.ID()).String() from := model.ElementID(relationship.From.ID()).String()
to := model.ElementID(relationship.To.ID()).String()
if from == packageSpdxID { if from == packageSpdxID {
to := model.ElementID(relationship.To.ID()).String()
fileIDs = append(fileIDs, to) fileIDs = append(fileIDs, to)
} }
} }
@ -125,7 +127,7 @@ func toFiles(s sbom.SBOM) []model.File {
results := make([]model.File, 0) results := make([]model.File, 0)
artifacts := s.Artifacts artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) { for _, coordinates := range s.AllCoordinates() {
var metadata *source.FileMetadata var metadata *source.FileMetadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation metadata = &metadataForLocation
@ -160,6 +162,9 @@ func toFiles(s sbom.SBOM) []model.File {
// sort by real path then virtual path to ensure the result is stable across multiple runs // sort by real path then virtual path to ensure the result is stable across multiple runs
sort.SliceStable(results, func(i, j int) bool { sort.SliceStable(results, func(i, j int) bool {
if results[i].FileName == results[j].FileName {
return results[i].SPDXID < results[j].SPDXID
}
return results[i].FileName < results[j].FileName return results[i].FileName < results[j].FileName
}) })
return results return results

View File

@ -93,7 +93,7 @@ func toFile(s sbom.SBOM) []model.File {
results := make([]model.File, 0) results := make([]model.File, 0)
artifacts := s.Artifacts artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) { for _, coordinates := range s.AllCoordinates() {
var metadata *source.FileMetadata var metadata *source.FileMetadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation metadata = &metadataForLocation

View File

@ -1,6 +1,8 @@
package sbom package sbom
import ( import (
"sort"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
@ -31,21 +33,35 @@ type Descriptor struct {
Configuration interface{} Configuration interface{}
} }
func AllCoordinates(sbom SBOM) []source.Coordinates { func (s SBOM) RelationshipsSorted() []artifact.Relationship {
relationships := s.Relationships
sort.SliceStable(relationships, func(i, j int) bool {
if relationships[i].From.ID() == relationships[j].From.ID() {
if relationships[i].To.ID() == relationships[j].To.ID() {
return relationships[i].Type < relationships[j].Type
}
return relationships[i].To.ID() < relationships[j].To.ID()
}
return relationships[i].From.ID() < relationships[j].From.ID()
})
return relationships
}
func (s SBOM) AllCoordinates() []source.Coordinates {
set := source.NewCoordinateSet() set := source.NewCoordinateSet()
for coordinates := range sbom.Artifacts.FileMetadata { for coordinates := range s.Artifacts.FileMetadata {
set.Add(coordinates) set.Add(coordinates)
} }
for coordinates := range sbom.Artifacts.FileContents { for coordinates := range s.Artifacts.FileContents {
set.Add(coordinates) set.Add(coordinates)
} }
for coordinates := range sbom.Artifacts.FileClassifications { for coordinates := range s.Artifacts.FileClassifications {
set.Add(coordinates) set.Add(coordinates)
} }
for coordinates := range sbom.Artifacts.FileDigests { for coordinates := range s.Artifacts.FileDigests {
set.Add(coordinates) set.Add(coordinates)
} }
for _, relationship := range sbom.Relationships { for _, relationship := range s.Relationships {
for _, coordinates := range extractCoordinates(relationship) { for _, coordinates := range extractCoordinates(relationship) {
set.Add(coordinates) set.Add(coordinates)
} }