fix: SPDX output performance with many relationships (#3053)

This commit is contained in:
Keith Zantow
2024-07-24 10:14:20 -04:00
committed by GitHub
parent 9573f557d1
commit 741c8fb9bd
9 changed files with 412 additions and 293 deletions
@@ -16,6 +16,7 @@ import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/relationship"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@@ -45,9 +46,10 @@ const (
func ToFormatModel(s sbom.SBOM) *spdx.Document {
name, namespace := helpers.DocumentNameAndNamespace(s.Source, s.Descriptor)
packages := toPackages(s.Artifacts.Packages, s)
rels := relationship.NewIndex(s.Relationships...)
packages := toPackages(rels, s.Artifacts.Packages, s)
relationships := toRelationships(s.RelationshipsSorted())
allRelationships := toRelationships(rels.All())
// for valid SPDX we need a document describes relationship
describesID := spdx.ElementID("DOCUMENT")
@@ -57,7 +59,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
describesID = rootPackage.PackageSPDXIdentifier
// add all relationships from the document root to all other packages
relationships = append(relationships, toRootRelationships(rootPackage, packages)...)
allRelationships = append(allRelationships, toRootRelationships(rootPackage, packages)...)
// append the root package
packages = append(packages, rootPackage)
@@ -75,7 +77,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
}
// add the root document relationship
relationships = append(relationships, documentDescribesRelationship)
allRelationships = append(allRelationships, documentDescribesRelationship)
return &spdx.Document{
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
@@ -150,7 +152,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
},
Packages: packages,
Files: toFiles(s),
Relationships: relationships,
Relationships: allRelationships,
OtherLicenses: toOtherLicenses(s.Artifacts.Packages),
}
}
@@ -302,7 +304,7 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
//
//nolint:funlen
func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package) {
func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package) {
for _, p := range catalog.Sorted() {
// name should be guaranteed to be unique, but semantically useful and stable
id := toSPDXID(p)
@@ -318,7 +320,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
// 2. syft has generated a sha1 digest for the package's contents
packageChecksums, filesAnalyzed := toPackageChecksums(p)
packageVerificationCode := newPackageVerificationCode(p, sbom)
packageVerificationCode := newPackageVerificationCode(rels, p, sbom)
if packageVerificationCode != nil {
filesAnalyzed = true
}
@@ -744,12 +746,12 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
// f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */
// see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field
// the above link contains the SPDX algorithm for a package verification code
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
func newPackageVerificationCode(rels *relationship.Index, p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
// key off of the contains relationship;
// spdx validator will fail if a package claims to contain a file but no sha1 provided
// if a sha1 for a file is provided then the validator will fail if the package does not have
// a package verification code
coordinates := sbom.CoordinatesForPackage(p, artifact.ContainsRelationship)
coordinates := rels.Coordinates(p, artifact.ContainsRelationship)
var digests []file.Digest
for _, c := range coordinates {
digest := sbom.Artifacts.FileDigests[c]
@@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/relationship"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/internal/spdxutil/helpers"
@@ -665,7 +666,7 @@ func Test_H1Digest(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
catalog := pkg.NewCollection(test.pkg)
pkgs := toPackages(catalog, s)
pkgs := toPackages(relationship.NewIndex(), catalog, s)
require.Len(t, pkgs, 1)
for _, p := range pkgs {
if test.expectedDigest == "" {
+4 -4
View File
@@ -167,7 +167,7 @@ func wheelEggRelationships(ctx context.Context, resolver file.Resolver, pkgs []p
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve relationships for global site package %q: %w", globalSitePackage, err)
}
relationshipIndex.AddAll(siteRels...)
relationshipIndex.Add(siteRels...)
}
// create relationships between packages within each virtual env site package directory (that doesn't link to a global site-packages directory)
@@ -180,7 +180,7 @@ func wheelEggRelationships(ctx context.Context, resolver file.Resolver, pkgs []p
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve relationships for virtualenv site package %q: %w", venv.SitePackagesPath, err)
}
relationshipIndex.AddAll(siteRels...)
relationshipIndex.Add(siteRels...)
}
// create relationships between packages within each virtual env site package directory (that links to a global site package directory)
@@ -197,10 +197,10 @@ func wheelEggRelationships(ctx context.Context, resolver file.Resolver, pkgs []p
return nil, nil, fmt.Errorf("failed to resolve relationships for virtualenv + global site package path %q + %q: %w", venv.SitePackagesPath, globalSitePackage, err)
}
relationshipIndex.AddAll(siteRels...)
relationshipIndex.Add(siteRels...)
}
return pkgs, relationshipIndex.AllUniqueRelationships(), err
return pkgs, relationshipIndex.All(), err
}
func collectPackages(pkgsBySitePackageAndName map[string]map[string]pkg.Package, sites []string) []pkg.Package {
+4
View File
@@ -35,6 +35,8 @@ type Descriptor struct {
Configuration interface{}
}
// RelationshipsSorted returns a sorted slice of all relationships
// Deprecated -- use relationship.Index
func (s SBOM) RelationshipsSorted() []artifact.Relationship {
relationships := s.Relationships
sort.SliceStable(relationships, func(i, j int) bool {
@@ -70,6 +72,7 @@ func (s SBOM) AllCoordinates() []file.Coordinates {
// RelationshipsForPackage returns all relationships for the provided types.
// If no types are provided, all relationships for the package are returned.
// Deprecated -- use relationship.Index
func (s SBOM) RelationshipsForPackage(p pkg.Package, rt ...artifact.RelationshipType) []artifact.Relationship {
if len(rt) == 0 {
rt = artifact.AllRelationshipTypes()
@@ -103,6 +106,7 @@ func (s SBOM) RelationshipsForPackage(p pkg.Package, rt ...artifact.Relationship
// CoordinatesForPackage returns all coordinates for the provided package for provided relationship types
// If no types are provided, all relationship types are considered.
// Deprecated -- use relationship.Index
func (s SBOM) CoordinatesForPackage(p pkg.Package, rt ...artifact.RelationshipType) []file.Coordinates {
var coordinates []file.Coordinates
for _, relationship := range s.RelationshipsForPackage(p, rt...) {