Skip to content

Commit

Permalink
Fixe syft formatting issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Moran authored and ryanmoran committed Feb 21, 2023
1 parent e4f7c46 commit e1f6949
Show file tree
Hide file tree
Showing 17 changed files with 415 additions and 104 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ go 1.16
require (
github.com/BurntSushi/toml v1.2.1
github.com/Masterminds/semver/v3 v3.2.0
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b
github.com/anchore/syft v0.72.0
github.com/apex/log v1.9.0
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
Expand All @@ -16,6 +18,7 @@ require (
github.com/pelletier/go-toml v1.9.5
github.com/sclevine/spec v1.4.0
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
github.com/sergi/go-diff v1.3.1
github.com/spdx/tools-golang v0.5.0-rc1
github.com/stretchr/testify v1.8.1
github.com/ulikunitz/xz v0.5.11
Expand Down
2 changes: 1 addition & 1 deletion sbom/formatted_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (f *FormattedReader) Read(b []byte) (int, error) {

// Makes CycloneDX SBOM more reproducible, see
// https://github.com/paketo-buildpacks/packit/issues/367 for more details.
if f.format.ID() == "cyclonedx-1.3-json" || f.format.ID() == "cyclonedx-1-json" {
if f.format.ID() == "cyclonedx-1.3-json" || f.format.ID() == "cyclonedx-json" {
var cycloneDXOutput map[string]interface{}
err = json.Unmarshal(output, &cycloneDXOutput)
if err != nil {
Expand Down
6 changes: 0 additions & 6 deletions sbom/formatted_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ func testFormattedReader(t *testing.T, context spec.G, it spec.S) {
format := syft.IdentifyFormat(buffer.Bytes())
Expect(format.ID()).To(Equal(syft.CycloneDxJSONFormatID))

// Ensures pretty printing
Expect(buffer.String()).To(ContainSubstring(`{
"bomFormat": "CycloneDX",
"components": [
{`))

var cdxOutput cdxOutput

err = json.Unmarshal(buffer.Bytes(), &cdxOutput)
Expand Down
309 changes: 309 additions & 0 deletions sbom/internal/formats/common/testutils/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
package testutils

import (
"bytes"
"math/rand"
"strings"
"testing"
"time"

"github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/go-testutils"
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)

type redactor func(s []byte) []byte

type imageCfg struct {
fromSnapshot bool
}

type ImageOption func(cfg *imageCfg)

func FromSnapshot() ImageOption {
return func(cfg *imageCfg) {
cfg.fromSnapshot = true
}
}

func AssertEncoderAgainstGoldenImageSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, testImage string, updateSnapshot bool, json bool, redactors ...redactor) {
var buffer bytes.Buffer

// grab the latest image contents and persist
if updateSnapshot {
imagetest.UpdateGoldenFixtureImage(t, testImage)
}

err := format.Encode(&buffer, sbom)
assert.NoError(t, err)
actual := buffer.Bytes()

// replace the expected snapshot contents with the current encoder contents
if updateSnapshot {
testutils.UpdateGoldenFileContents(t, actual)
}

var expected = testutils.GetGoldenFileContents(t)

// remove dynamic values, which should be tested independently
redactors = append(redactors, carriageRedactor)
for _, r := range redactors {
actual = r(actual)
expected = r(expected)
}

if json {
require.JSONEq(t, string(expected), string(actual))
} else if !bytes.Equal(expected, actual) {
// assert that the golden file snapshot matches the actual contents
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true)
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
}
}

func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom sbom.SBOM, updateSnapshot bool, json bool, redactors ...redactor) {
var buffer bytes.Buffer

err := format.Encode(&buffer, sbom)
assert.NoError(t, err)
actual := buffer.Bytes()

// replace the expected snapshot contents with the current encoder contents
if updateSnapshot {
testutils.UpdateGoldenFileContents(t, actual)
}

var expected = testutils.GetGoldenFileContents(t)

// remove dynamic values, which should be tested independently
redactors = append(redactors, carriageRedactor)
for _, r := range redactors {
actual = r(actual)
expected = r(expected)
}

if json {
require.JSONEq(t, string(expected), string(actual))
} else if !bytes.Equal(expected, actual) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(expected), string(actual), true)
t.Logf("len: %d\nexpected: %s", len(expected), expected)
t.Logf("len: %d\nactual: %s", len(actual), actual)
t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs))
}
}

func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBOM {
t.Helper()
catalog := pkg.NewCatalog()
var cfg imageCfg
var img *image.Image
for _, opt := range options {
opt(&cfg)
}

switch cfg.fromSnapshot {
case true:
img = imagetest.GetGoldenFixtureImage(t, testImage)
default:
img = imagetest.GetFixtureImage(t, "docker-archive", testImage)
}

populateImageCatalog(catalog, img)

// this is a hard coded value that is not given by the fixture helper and must be provided manually
img.Metadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"

src, err := source.NewFromImage(img, "user-image-input")
assert.NoError(t, err)

return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
LinuxDistribution: &linux.Release{
PrettyName: "debian",
Name: "debian",
ID: "debian",
IDLike: []string{"like!"},
Version: "1.2.3",
VersionID: "1.2.3",
},
},
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: "syft",
Version: "v0.42.0-bogus",
// the application configuration should be persisted here, however, we do not want to import
// the application configuration in this package (it's reserved only for ingestion by the cmd package)
Configuration: map[string]string{
"config-key": "config-value",
},
},
}
}

func carriageRedactor(s []byte) []byte {
msg := strings.ReplaceAll(string(s), "\r\n", "\n")
return []byte(msg)
}

func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)

// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
Version: "1.0.1",
Locations: source.NewLocationSet(
source.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img),
),
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
},
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: source.NewLocationSet(
source.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img),
),
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
},
PURL: "pkg:deb/debian/[email protected]",
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
}

func DirectoryInput(t testing.TB) sbom.SBOM {
catalog := newDirectoryCatalog()

src, err := source.NewFromDirectory("/some/path")
assert.NoError(t, err)

return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
LinuxDistribution: &linux.Release{
PrettyName: "debian",
Name: "debian",
ID: "debian",
IDLike: []string{"like!"},
Version: "1.2.3",
VersionID: "1.2.3",
},
},
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: "syft",
Version: "v0.42.0-bogus",
// the application configuration should be persisted here, however, we do not want to import
// the application configuration in this package (it's reserved only for ingestion by the cmd package)
Configuration: map[string]string{
"config-key": "config-value",
},
},
}
}

func newDirectoryCatalog() *pkg.Catalog {
catalog := pkg.NewCatalog()

// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
Version: "1.0.1",
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Locations: source.NewLocationSet(
source.NewLocation("/some/path/pkg1"),
),
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: []string{"MIT"},
Metadata: pkg.PythonPackageMetadata{
Name: "package-1",
Version: "1.0.1",
Files: []pkg.PythonFileRecord{
{
Path: "/some/path/pkg1/dependencies/foo",
},
},
},
PURL: "a-purl-2", // intentionally a bad pURL for test fixtures
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
Locations: source.NewLocationSet(
source.NewLocation("/some/path/pkg1"),
),
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
Package: "package-2",
Version: "2.0.1",
},
PURL: "pkg:deb/debian/[email protected]",
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})

return catalog
}

//nolint:gosec
func AddSampleFileRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted()
s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{}

files := []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"}
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
rnd.Shuffle(len(files), func(i, j int) { files[i], files[j] = files[j], files[i] })

for _, f := range files {
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,
})
}
}
2 changes: 1 addition & 1 deletion sbom/internal/formats/cyclonedx13/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"regexp"
"testing"

"github.com/anchore/syft/syft/formats/common/testutils"
"github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/common/testutils"
)

var updateCycloneDx = flag.Bool("update-cyclonedx", false, "update the *.golden files for cyclone-dx encoders")
Expand Down
15 changes: 4 additions & 11 deletions sbom/internal/formats/cyclonedx13/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,12 @@ import (
// TODO: Decide version granularity of IDs
const ID sbom.FormatID = "cyclonedx-1.3-json"

var dummyDecoder func(io.Reader) (*sbom.SBOM, error) = func(input io.Reader) (*sbom.SBOM, error) {
return nil, nil
}

var dummyValidator func(io.Reader) error = func(input io.Reader) error {
return nil
}

func Format() sbom.Format {
return sbom.NewFormat(
ID,
sbom.AnyVersion,
encoder,
dummyDecoder,
dummyValidator,
func(input io.Reader) (*sbom.SBOM, error) { return nil, nil },
func(input io.Reader) error { return nil },
ID,
)
}
2 changes: 1 addition & 1 deletion sbom/internal/formats/spdx22/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"regexp"
"testing"

"github.com/anchore/syft/syft/formats/common/testutils"
"github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/common/testutils"
)

var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json encoders")
Expand Down
Loading

0 comments on commit e1f6949

Please sign in to comment.