Skip to content

Commit

Permalink
Stabilize SPDX JSON output sorting (#1216)
Browse files Browse the repository at this point in the history
  • Loading branch information
kzantow committed Sep 19, 2022
1 parent 0f99215 commit c2005fa
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 11 deletions.
33 changes: 33 additions & 0 deletions syft/formats/spdx22json/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import (
"regexp"
"testing"

"github.com/anchore/syft/syft/artifact"
"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")
Expand All @@ -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 {
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
13 changes: 9 additions & 4 deletions syft/formats/spdx22json/to_format_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
func toFormatModel(s sbom.SBOM) *model.Document {
name, namespace := spdxhelpers.DocumentNameAndNamespace(s.Source)

relationships := s.RelationshipsSorted()

return &model.Document{
Element: model.Element{
SPDXID: model.ElementID("DOCUMENT").String(),
Expand All @@ -39,9 +41,9 @@ func toFormatModel(s sbom.SBOM) *model.Document {
},
DataLicense: "CC0-1.0",
DocumentNamespace: namespace,
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships),
Packages: toPackages(s.Artifacts.PackageCatalog, relationships),
Files: toFiles(s),
Relationships: toRelationships(s.Relationships),
Relationships: toRelationships(relationships),
}
}

Expand Down Expand Up @@ -113,8 +115,8 @@ func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationsh
}

from := model.ElementID(relationship.From.ID()).String()
to := model.ElementID(relationship.To.ID()).String()
if from == packageSpdxID {
to := model.ElementID(relationship.To.ID()).String()
fileIDs = append(fileIDs, to)
}
}
Expand All @@ -125,7 +127,7 @@ func toFiles(s sbom.SBOM) []model.File {
results := make([]model.File, 0)
artifacts := s.Artifacts

for _, coordinates := range sbom.AllCoordinates(s) {
for _, coordinates := range s.AllCoordinates() {
var metadata *source.FileMetadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation
Expand Down Expand Up @@ -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.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
Expand Down
2 changes: 1 addition & 1 deletion syft/formats/syftjson/to_format_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func toFile(s sbom.SBOM) []model.File {
results := make([]model.File, 0)
artifacts := s.Artifacts

for _, coordinates := range sbom.AllCoordinates(s) {
for _, coordinates := range s.AllCoordinates() {
var metadata *source.FileMetadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation
Expand Down
28 changes: 22 additions & 6 deletions syft/sbom/sbom.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package sbom

import (
"sort"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
Expand Down Expand Up @@ -31,21 +33,35 @@ type Descriptor struct {
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()
for coordinates := range sbom.Artifacts.FileMetadata {
for coordinates := range s.Artifacts.FileMetadata {
set.Add(coordinates)
}
for coordinates := range sbom.Artifacts.FileContents {
for coordinates := range s.Artifacts.FileContents {
set.Add(coordinates)
}
for coordinates := range sbom.Artifacts.FileClassifications {
for coordinates := range s.Artifacts.FileClassifications {
set.Add(coordinates)
}
for coordinates := range sbom.Artifacts.FileDigests {
for coordinates := range s.Artifacts.FileDigests {
set.Add(coordinates)
}
for _, relationship := range sbom.Relationships {
for _, relationship := range s.Relationships {
for _, coordinates := range extractCoordinates(relationship) {
set.Add(coordinates)
}
Expand Down

0 comments on commit c2005fa

Please sign in to comment.