diff --git a/syft/formats/common/cyclonedxhelpers/format.go b/syft/formats/common/cyclonedxhelpers/format.go index 9582ba85d3ef..e36149e1280b 100644 --- a/syft/formats/common/cyclonedxhelpers/format.go +++ b/syft/formats/common/cyclonedxhelpers/format.go @@ -5,6 +5,7 @@ import ( "github.com/CycloneDX/cyclonedx-go" "github.com/google/uuid" + "golang.org/x/exp/slices" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" @@ -139,7 +140,7 @@ func isExpressiblePackageRelationship(ty artifact.RelationshipType) bool { } func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependency { - result := make([]cyclonedx.Dependency, 0) + dependencies := map[string]*cyclonedx.Dependency{} for _, r := range relationships { exists := isExpressiblePackageRelationship(r.Type) if !exists { @@ -160,15 +161,32 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc continue } - // ind dep + toRef := deriveBomRef(toPkg) + dep := dependencies[toRef] + if dep == nil { + dep = &cyclonedx.Dependency{ + Ref: toRef, + Dependencies: &[]string{}, + } + dependencies[toRef] = dep + } - innerDeps := []string{} - innerDeps = append(innerDeps, deriveBomRef(fromPkg)) - result = append(result, cyclonedx.Dependency{ - Ref: deriveBomRef(toPkg), - Dependencies: &innerDeps, - }) + fromRef := deriveBomRef(fromPkg) + if !slices.Contains(*dep.Dependencies, fromRef) { + *dep.Dependencies = append(*dep.Dependencies, fromRef) + } } + + result := make([]cyclonedx.Dependency, 0, len(dependencies)) + for _, dep := range dependencies { + slices.Sort(*dep.Dependencies) + result = append(result, *dep) + } + + slices.SortFunc(result, func(a, b cyclonedx.Dependency) bool { + return a.Ref < b.Ref + }) + return result } diff --git a/syft/formats/common/cyclonedxhelpers/format_test.go b/syft/formats/common/cyclonedxhelpers/format_test.go index d16a390a6333..c62afb8f42fd 100644 --- a/syft/formats/common/cyclonedxhelpers/format_test.go +++ b/syft/formats/common/cyclonedxhelpers/format_test.go @@ -1,6 +1,7 @@ package cyclonedxhelpers import ( + "fmt" "testing" "github.com/CycloneDX/cyclonedx-go" @@ -43,28 +44,34 @@ func Test_relationships(t *testing.T) { p1 := pkg.Package{ Name: "p1", } - p1.SetID() p2 := pkg.Package{ Name: "p2", } - p2.SetID() p3 := pkg.Package{ Name: "p3", } - p3.SetID() + + p4 := pkg.Package{ + Name: "p4", + } + + for _, p := range []*pkg.Package{&p1, &p2, &p3, &p4} { + p.PURL = fmt.Sprintf("pkg:generic/%s@%s", p.Name, p.Name) + p.SetID() + } tests := []struct { name string sbom sbom.SBOM - expected []string + expected *[]cyclonedx.Dependency }{ { name: "package dependencyOf relationships output as dependencies", sbom: sbom.SBOM{ Artifacts: sbom.Artifacts{ - Packages: pkg.NewCollection(p1, p2, p3), + Packages: pkg.NewCollection(p1, p2, p3, p4), }, Relationships: []artifact.Relationship{ { @@ -77,9 +84,28 @@ func Test_relationships(t *testing.T) { To: p1, Type: artifact.DependencyOfRelationship, }, + { + From: p4, + To: p2, + Type: artifact.DependencyOfRelationship, + }, + }, + }, + expected: &[]cyclonedx.Dependency{ + { + Ref: deriveBomRef(p1), + Dependencies: &[]string{ + deriveBomRef(p2), + deriveBomRef(p3), + }, + }, + { + Ref: deriveBomRef(p2), + Dependencies: &[]string{ + deriveBomRef(p4), + }, }, }, - expected: []string{p2.Name, p3.Name}, }, { name: "package contains relationships not output", @@ -108,28 +134,7 @@ func Test_relationships(t *testing.T) { t.Run(test.name, func(t *testing.T) { cdx := ToFormatModel(test.sbom) got := cdx.Dependencies - - var deps []string - if got != nil { - for _, r := range *got { - for _, d := range *r.Dependencies { - c := findComponent(cdx, d) - require.NotNil(t, c) - deps = append(deps, c.Name) - } - - } - } - require.Equal(t, test.expected, deps) + require.Equal(t, test.expected, got) }) } } - -func findComponent(cdx *cyclonedx.BOM, bomRef string) *cyclonedx.Component { - for _, c := range *cdx.Components { - if c.BOMRef == bomRef { - return &c - } - } - return nil -}