Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions pkg/sbom/generator/spdx/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
Expand Down Expand Up @@ -254,9 +255,15 @@ func (sx *SPDX) ProcessInternalApkSBOM(ctx context.Context, opts *options.Option
}
}

// Copy the targetElementIDs
todo := make(map[string]struct{}, len(apkSBOMDoc.Relationships))
sortedTargetElementIDs := make([]string, 0, len(targetElementIDs))
for id := range targetElementIDs {
sortedTargetElementIDs = append(sortedTargetElementIDs, id)
}
// Sort the element IDs so repeated builds produce the same relationship order.
sort.Strings(sortedTargetElementIDs)

todo := make(map[string]struct{}, len(apkSBOMDoc.Relationships))
for _, id := range sortedTargetElementIDs {
todo[id] = struct{}{}
}

Expand All @@ -270,7 +277,7 @@ func (sx *SPDX) ProcessInternalApkSBOM(ctx context.Context, opts *options.Option
// This ensures they are reachable from the document root for tools that traverse the SBOM graph.
if len(doc.DocumentDescribes) > 0 {
rootPkgID := doc.DocumentDescribes[0]
for elementID := range targetElementIDs {
for _, elementID := range sortedTargetElementIDs {
doc.Relationships = append(doc.Relationships, Relationship{
Element: rootPkgID,
Type: "CONTAINS",
Expand Down
54 changes: 47 additions & 7 deletions pkg/sbom/generator/spdx/spdx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,23 +271,63 @@ func TestSPDX_Generate(t *testing.T) {
}

func TestReproducible(t *testing.T) {
// Create two sboms based on the same input and ensure
// they are identical
// Create SBOMs based on the same input and ensure they are identical.
const (
packageID = "SPDXRef-Package-glibc-2.40-r0"
documentRootID = "SPDXRef-DocumentRoot-Directory-glibc"
internalSBOMPath = "/var/lib/db/sbom/glibc-2.40-r0.spdx.json"
)

dir := t.TempDir()
fsys := apkfs.NewMemFS()
opts := testOpts(fsys)
opts.Packages = []*apk.InstalledPackage{{Name: "glibc", Version: "2.40-r0"}}

internalSBOM := Document{
DocumentDescribes: []string{
packageID,
documentRootID,
},
Packages: []Package{
{ID: packageID, Name: "glibc"},
{ID: documentRootID, Name: "/"},
},
}
data, err := json.Marshal(internalSBOM)
require.NoError(t, err)
require.NoError(t, fsys.MkdirAll("/var/lib/db/sbom", 0750))
require.NoError(t, fsys.WriteFile(internalSBOMPath, data, 0644))

sx := New()
d := make([][]byte, 0, 2)
for i := range 2 {
generate := func(i int) []byte {
path := filepath.Join(dir, fmt.Sprintf("sbom%d.%s", i, sx.Ext()))
require.NoError(t, sx.Generate(t.Context(), opts, path))
require.FileExists(t, path)
data, err := os.ReadFile(path)
require.NoError(t, err)
d = append(d, data)
return data
}

expected := generate(0)
var doc Document
require.NoError(t, json.Unmarshal(expected, &doc))
require.Contains(t, doc.Relationships, Relationship{
Element: doc.DocumentDescribes[0],
Type: "CONTAINS",
Related: packageID,
})
require.Contains(t, doc.Relationships, Relationship{
Element: doc.DocumentDescribes[0],
Type: "CONTAINS",
Related: documentRootID,
})

// Exercise enough new maps to detect unstable iteration order if the relationships are not sorted.
for i := 1; i < 100; i++ {
if diff := cmp.Diff(expected, generate(i)); diff != "" {
t.Fatalf("SBOM differs from first generation (-want +got):\n%s", diff)
}
}
diff := cmp.Diff(d[0], d[1])
require.Empty(t, diff, fmt.Sprintf("difference in expected output %s", diff))
}

// To run TestValidateSPDX, point SPDX_TOOLS_JAR to the SPDX tools
Expand Down