mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Stabilize SPDX JSON output sorting (#1216)
This commit is contained in:
parent
0f99215b2c
commit
c2005fad8d
@ -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")
|
||||
@ -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"))
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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(),
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user