From e1f694912fab0d68f06defe5271ce980ade591aa Mon Sep 17 00:00:00 2001 From: Ryan Moran Date: Tue, 21 Feb 2023 15:14:09 -0800 Subject: [PATCH] Fixe syft formatting issues --- go.mod | 3 + sbom/formatted_reader.go | 2 +- sbom/formatted_reader_test.go | 6 - .../formats/common/testutils/utils.go | 309 ++++++++++++++++++ .../formats/cyclonedx13/encoder_test.go | 2 +- sbom/internal/formats/cyclonedx13/format.go | 15 +- sbom/internal/formats/spdx22/encoder_test.go | 2 +- sbom/internal/formats/spdx22/format.go | 17 +- sbom/internal/formats/syft2/encoder_test.go | 16 +- sbom/internal/formats/syft2/format.go | 17 +- .../TestEncodeFullJSONDocument.golden | 8 +- .../internal/formats/syft2/to_format_model.go | 37 ++- sbom/internal/formats/syft301/encoder_test.go | 16 +- sbom/internal/formats/syft301/format.go | 17 +- .../TestEncodeFullJSONDocument.golden | 8 +- .../formats/syft301/to_format_model.go | 42 ++- sbom/sbom_format.go | 2 +- 17 files changed, 415 insertions(+), 104 deletions(-) create mode 100644 sbom/internal/formats/common/testutils/utils.go diff --git a/go.mod b/go.mod index b9e146a7..a02fffc2 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/sbom/formatted_reader.go b/sbom/formatted_reader.go index ba648d98..a4462328 100644 --- a/sbom/formatted_reader.go +++ b/sbom/formatted_reader.go @@ -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 { diff --git a/sbom/formatted_reader_test.go b/sbom/formatted_reader_test.go index 0f388542..bfd79b23 100644 --- a/sbom/formatted_reader_test.go +++ b/sbom/formatted_reader_test.go @@ -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) diff --git a/sbom/internal/formats/common/testutils/utils.go b/sbom/internal/formats/common/testutils/utils.go new file mode 100644 index 00000000..f214c4f0 --- /dev/null +++ b/sbom/internal/formats/common/testutils/utils.go @@ -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/package-2@2.0.1", + 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/package-2@2.0.1", + 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, + }) + } +} diff --git a/sbom/internal/formats/cyclonedx13/encoder_test.go b/sbom/internal/formats/cyclonedx13/encoder_test.go index d893d352..031c38bc 100644 --- a/sbom/internal/formats/cyclonedx13/encoder_test.go +++ b/sbom/internal/formats/cyclonedx13/encoder_test.go @@ -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") diff --git a/sbom/internal/formats/cyclonedx13/format.go b/sbom/internal/formats/cyclonedx13/format.go index aec773c7..b60c3b31 100644 --- a/sbom/internal/formats/cyclonedx13/format.go +++ b/sbom/internal/formats/cyclonedx13/format.go @@ -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, ) } diff --git a/sbom/internal/formats/spdx22/encoder_test.go b/sbom/internal/formats/spdx22/encoder_test.go index a80910f0..56177343 100644 --- a/sbom/internal/formats/spdx22/encoder_test.go +++ b/sbom/internal/formats/spdx22/encoder_test.go @@ -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") diff --git a/sbom/internal/formats/spdx22/format.go b/sbom/internal/formats/spdx22/format.go index 1fdf1131..95bee545 100644 --- a/sbom/internal/formats/spdx22/format.go +++ b/sbom/internal/formats/spdx22/format.go @@ -8,21 +8,12 @@ import ( const ID sbom.FormatID = "spdx-2-json" -// Decoder not implemented because it's not needed for buildpacks' SBOM generation -var dummyDecoder func(io.Reader) (*sbom.SBOM, error) = func(input io.Reader) (*sbom.SBOM, error) { - return nil, nil -} - -// Validator not implemented because it's not needed for buildpacks' SBOM generation -var dummyValidator func(io.Reader) error = func(input io.Reader) error { - return nil -} - func Format() sbom.Format { return sbom.NewFormat( - ID, + "2.2", encoder, - dummyDecoder, - dummyValidator, + func(input io.Reader) (*sbom.SBOM, error) { return nil, nil }, + func(input io.Reader) error { return nil }, + ID, ) } diff --git a/sbom/internal/formats/syft2/encoder_test.go b/sbom/internal/formats/syft2/encoder_test.go index b67573e2..32c6037a 100644 --- a/sbom/internal/formats/syft2/encoder_test.go +++ b/sbom/internal/formats/syft2/encoder_test.go @@ -4,17 +4,15 @@ import ( "flag" "testing" + stereoFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" - - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" - - "github.com/anchore/syft/syft/formats/common/testutils" + "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/common/testutils" ) // Source https://github.com/anchore/syft/blob/dfefd2ea4e9d44187b4f861bc202970247dd34c8/internal/formats/syftjson/encoder_test.go @@ -103,26 +101,26 @@ func TestEncodeFullJSONDocument(t *testing.T) { FileMetadata: map[source.Coordinates]source.FileMetadata{ source.NewLocation("/a/place").Coordinates: { Mode: 0775, - Type: "directory", + Type: stereoFile.TypeDirectory, UserID: 0, GroupID: 0, }, source.NewLocation("/a/place/a").Coordinates: { Mode: 0775, - Type: "regularFile", + Type: stereoFile.TypeRegular, UserID: 0, GroupID: 0, }, source.NewLocation("/b").Coordinates: { Mode: 0775, - Type: "symbolicLink", + Type: stereoFile.TypeSymLink, LinkDestination: "/c", UserID: 0, GroupID: 0, }, source.NewLocation("/b/place/b").Coordinates: { Mode: 0644, - Type: "regularFile", + Type: stereoFile.TypeRegular, UserID: 1, GroupID: 2, }, diff --git a/sbom/internal/formats/syft2/format.go b/sbom/internal/formats/syft2/format.go index 256c17f2..d1e33114 100644 --- a/sbom/internal/formats/syft2/format.go +++ b/sbom/internal/formats/syft2/format.go @@ -9,21 +9,12 @@ import ( const ID sbom.FormatID = "syft-2.0-json" const JSONSchemaVersion string = "2.0.2" -// Decoder not implemented because it's not needed for buildpacks' SBOM generation -var dummyDecoder func(io.Reader) (*sbom.SBOM, error) = func(input io.Reader) (*sbom.SBOM, error) { - return nil, nil -} - -// Validator not implemented because it's not needed for buildpacks' SBOM generation -var dummyValidator func(io.Reader) error = func(input io.Reader) error { - return nil -} - func Format() sbom.Format { return sbom.NewFormat( - ID, + "2.0.2", encoder, - dummyDecoder, - dummyValidator, + func(input io.Reader) (*sbom.SBOM, error) { return nil, nil }, + func(input io.Reader) error { return nil }, + ID, ) } diff --git a/sbom/internal/formats/syft2/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/sbom/internal/formats/syft2/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index 620cb151..b3f23b6d 100644 --- a/sbom/internal/formats/syft2/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/sbom/internal/formats/syft2/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -78,7 +78,7 @@ }, "metadata": { "mode": 775, - "type": "directory", + "type": "Directory", "userID": 0, "groupID": 0, "mimeType": "" @@ -91,7 +91,7 @@ }, "metadata": { "mode": 775, - "type": "regularFile", + "type": "RegularFile", "userID": 0, "groupID": 0, "mimeType": "" @@ -111,7 +111,7 @@ }, "metadata": { "mode": 775, - "type": "symbolicLink", + "type": "SymbolicLink", "linkDestination": "/c", "userID": 0, "groupID": 0, @@ -125,7 +125,7 @@ }, "metadata": { "mode": 644, - "type": "regularFile", + "type": "RegularFile", "userID": 1, "groupID": 2, "mimeType": "" diff --git a/sbom/internal/formats/syft2/to_format_model.go b/sbom/internal/formats/syft2/to_format_model.go index b48da3f3..eeeca129 100644 --- a/sbom/internal/formats/syft2/to_format_model.go +++ b/sbom/internal/formats/syft2/to_format_model.go @@ -5,16 +5,14 @@ import ( "sort" "strconv" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/linux" - - "github.com/anchore/syft/syft/artifact" - - "github.com/anchore/syft/syft/sbom" - "github.com/anchore/syft/syft/formats/syftjson/model" + "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" internalmodel "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/syft2/model" syft2source "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/syft2/source" @@ -117,7 +115,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe return &model.FileMetadataEntry{ Mode: mode, - Type: metadata.Type, + Type: toFileType(metadata.Type), LinkDestination: metadata.LinkDestination, UserID: metadata.UserID, GroupID: metadata.GroupID, @@ -125,6 +123,31 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe } } +func toFileType(ty stereoscopeFile.Type) string { + switch ty { + case stereoscopeFile.TypeSymLink: + return "SymbolicLink" + case stereoscopeFile.TypeHardLink: + return "HardLink" + case stereoscopeFile.TypeDirectory: + return "Directory" + case stereoscopeFile.TypeSocket: + return "Socket" + case stereoscopeFile.TypeBlockDevice: + return "BlockDevice" + case stereoscopeFile.TypeCharacterDevice: + return "CharacterDevice" + case stereoscopeFile.TypeFIFO: + return "FIFONode" + case stereoscopeFile.TypeRegular: + return "RegularFile" + case stereoscopeFile.TypeIrregular: + return "IrregularFile" + default: + return "Unknown" + } +} + func toPackageModels(catalog *pkg.Catalog) []internalmodel.Package { artifacts := make([]internalmodel.Package, 0) if catalog == nil { diff --git a/sbom/internal/formats/syft301/encoder_test.go b/sbom/internal/formats/syft301/encoder_test.go index f859a193..261aeca6 100644 --- a/sbom/internal/formats/syft301/encoder_test.go +++ b/sbom/internal/formats/syft301/encoder_test.go @@ -4,17 +4,15 @@ import ( "flag" "testing" + stereoFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" - - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" - - "github.com/anchore/syft/syft/formats/common/testutils" + "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/common/testutils" ) var updateJson = flag.Bool("update-json", false, "update the *.golden files for json encoders") @@ -101,26 +99,26 @@ func TestEncodeFullJSONDocument(t *testing.T) { FileMetadata: map[source.Coordinates]source.FileMetadata{ source.NewLocation("/a/place").Coordinates: { Mode: 0775, - Type: "directory", + Type: stereoFile.TypeDirectory, UserID: 0, GroupID: 0, }, source.NewLocation("/a/place/a").Coordinates: { Mode: 0775, - Type: "regularFile", + Type: stereoFile.TypeRegular, UserID: 0, GroupID: 0, }, source.NewLocation("/b").Coordinates: { Mode: 0775, - Type: "symbolicLink", + Type: stereoFile.TypeSymLink, LinkDestination: "/c", UserID: 0, GroupID: 0, }, source.NewLocation("/b/place/b").Coordinates: { Mode: 0644, - Type: "regularFile", + Type: stereoFile.TypeRegular, UserID: 1, GroupID: 2, }, diff --git a/sbom/internal/formats/syft301/format.go b/sbom/internal/formats/syft301/format.go index 7b7ce8bd..6b3ef02b 100644 --- a/sbom/internal/formats/syft301/format.go +++ b/sbom/internal/formats/syft301/format.go @@ -9,21 +9,12 @@ import ( const ID sbom.FormatID = "syft-3.0.1-json" const JSONSchemaVersion string = "3.0.1" -// Decoder not implemented because it's not needed for buildpacks' SBOM generation -var dummyDecoder func(io.Reader) (*sbom.SBOM, error) = func(input io.Reader) (*sbom.SBOM, error) { - return nil, nil -} - -// Validator not implemented because it's not needed for buildpacks' SBOM generation -var dummyValidator func(io.Reader) error = func(input io.Reader) error { - return nil -} - func Format() sbom.Format { return sbom.NewFormat( - ID, + "3.0.1", encoder, - dummyDecoder, - dummyValidator, + func(input io.Reader) (*sbom.SBOM, error) { return nil, nil }, + func(input io.Reader) error { return nil }, + ID, ) } diff --git a/sbom/internal/formats/syft301/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden b/sbom/internal/formats/syft301/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden index e687e437..c1f898d7 100644 --- a/sbom/internal/formats/syft301/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden +++ b/sbom/internal/formats/syft301/test-fixtures/snapshot/TestEncodeFullJSONDocument.golden @@ -78,7 +78,7 @@ }, "metadata": { "mode": 775, - "type": "directory", + "type": "Directory", "userID": 0, "groupID": 0, "mimeType": "" @@ -91,7 +91,7 @@ }, "metadata": { "mode": 775, - "type": "regularFile", + "type": "RegularFile", "userID": 0, "groupID": 0, "mimeType": "" @@ -111,7 +111,7 @@ }, "metadata": { "mode": 775, - "type": "symbolicLink", + "type": "SymbolicLink", "linkDestination": "/c", "userID": 0, "groupID": 0, @@ -125,7 +125,7 @@ }, "metadata": { "mode": 644, - "type": "regularFile", + "type": "RegularFile", "userID": 1, "groupID": 2, "mimeType": "" diff --git a/sbom/internal/formats/syft301/to_format_model.go b/sbom/internal/formats/syft301/to_format_model.go index 5b5ae8f5..d8e8a5fa 100644 --- a/sbom/internal/formats/syft301/to_format_model.go +++ b/sbom/internal/formats/syft301/to_format_model.go @@ -5,21 +5,16 @@ import ( "sort" "strconv" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/linux" - "github.com/anchore/syft/syft/file" - - "github.com/anchore/syft/syft/artifact" - - "github.com/anchore/syft/syft/sbom" - - internalmodel "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/syft301/model" - "github.com/anchore/syft/syft/formats/syftjson/model" - // "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" + internalmodel "github.com/paketo-buildpacks/packit/v2/sbom/internal/formats/syft301/model" ) // ToFormatModel transforms the sbom import a format-specific model. @@ -141,7 +136,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe return &model.FileMetadataEntry{ Mode: mode, - Type: metadata.Type, + Type: toFileType(metadata.Type), LinkDestination: metadata.LinkDestination, UserID: metadata.UserID, GroupID: metadata.GroupID, @@ -149,6 +144,31 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe } } +func toFileType(ty stereoscopeFile.Type) string { + switch ty { + case stereoscopeFile.TypeSymLink: + return "SymbolicLink" + case stereoscopeFile.TypeHardLink: + return "HardLink" + case stereoscopeFile.TypeDirectory: + return "Directory" + case stereoscopeFile.TypeSocket: + return "Socket" + case stereoscopeFile.TypeBlockDevice: + return "BlockDevice" + case stereoscopeFile.TypeCharacterDevice: + return "CharacterDevice" + case stereoscopeFile.TypeFIFO: + return "FIFONode" + case stereoscopeFile.TypeRegular: + return "RegularFile" + case stereoscopeFile.TypeIrregular: + return "IrregularFile" + default: + return "Unknown" + } +} + func toPackageModels(catalog *pkg.Catalog) []model.Package { artifacts := make([]model.Package, 0) if catalog == nil { diff --git a/sbom/sbom_format.go b/sbom/sbom_format.go index cb5ee5f7..ccc3b97e 100644 --- a/sbom/sbom_format.go +++ b/sbom/sbom_format.go @@ -57,7 +57,7 @@ func (f sbomFormat) Extension() string { switch f.ID() { case syft.CycloneDxJSONFormatID, cyclonedx13.ID: return "cdx.json" - case syft.SPDXJSONFormatID: + case syft.SPDXJSONFormatID, spdx22.ID: return "spdx.json" case syft.JSONFormatID, syft2.ID, syft301.ID: return "syft.json"