package syftjson import ( "flag" "testing" stereoFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/format/internal/testutil" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" ) var updateSnapshot = flag.Bool("update-json", false, "update the *.golden files for json encoders") var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing") func TestDefaultNameAndVersion(t *testing.T) { expectedID, expectedVersion := ID, internal.JSONSchemaVersion enc := NewFormatEncoder() if enc.ID() != expectedID { t.Errorf("expected ID %q, got %q", expectedID, enc.ID()) } if enc.Version() != expectedVersion { t.Errorf("expected version %q, got %q", expectedVersion, enc.Version()) } } func TestDirectoryEncoder(t *testing.T) { dir := t.TempDir() testutil.AssertEncoderAgainstGoldenSnapshot(t, testutil.EncoderSnapshotTestConfig{ Subject: testutil.DirectoryInput(t, dir), Format: NewFormatEncoder(), UpdateSnapshot: *updateSnapshot, PersistRedactionsInSnapshot: true, IsJSON: true, Redactor: redactor(dir), }, ) } func TestImageEncoder(t *testing.T) { testImage := "image-simple" testutil.AssertEncoderAgainstGoldenImageSnapshot(t, testutil.ImageSnapshotTestConfig{ Image: testImage, UpdateImageSnapshot: *updateImage, }, testutil.EncoderSnapshotTestConfig{ Subject: testutil.ImageInput(t, testImage, testutil.FromSnapshot()), Format: NewFormatEncoder(), UpdateSnapshot: *updateSnapshot, PersistRedactionsInSnapshot: true, IsJSON: true, Redactor: redactor(), }, ) } func TestEncodeFullJSONDocument(t *testing.T) { catalog := pkg.NewCollection() p1 := pkg.Package{ Name: "package-1", Version: "1.0.1", Locations: file.NewLocationSet( file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/a/place/a", }), ), Type: pkg.PythonPkg, FoundBy: "the-cataloger-1", Language: pkg.Python, Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")), Metadata: pkg.PythonPackage{ Name: "package-1", Version: "1.0.1", Files: []pkg.PythonFileRecord{}, }, PURL: "a-purl-1", CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"), }, } p2 := pkg.Package{ Name: "package-2", Version: "2.0.1", Locations: file.NewLocationSet( file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/b/place/b", }), ), Type: pkg.DebPkg, FoundBy: "the-cataloger-2", Metadata: pkg.DpkgDBEntry{ Package: "package-2", Version: "2.0.1", Files: []pkg.DpkgFileRecord{}, }, PURL: "a-purl-2", CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"), }, } catalog.Add(p1) catalog.Add(p2) s := sbom.SBOM{ Artifacts: sbom.Artifacts{ Packages: catalog, FileMetadata: map[file.Coordinates]file.Metadata{ file.NewVirtualLocation("/a/place", "/a/symlink/to/place").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/a/place", ModeValue: 0775, }, Type: stereoFile.TypeDirectory, UserID: 0, GroupID: 0, }, file.NewLocation("/a/place/a").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/a/place/a", ModeValue: 0775, }, Type: stereoFile.TypeRegular, UserID: 0, GroupID: 0, }, file.NewLocation("/b").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/b", ModeValue: 0775, }, Type: stereoFile.TypeSymLink, LinkDestination: "/c", UserID: 0, GroupID: 0, }, file.NewLocation("/b/place/b").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/b/place/b", ModeValue: 0644, }, Type: stereoFile.TypeRegular, UserID: 1, GroupID: 2, }, }, FileDigests: map[file.Coordinates][]file.Digest{ file.NewLocation("/a/place/a").Coordinates: { { Algorithm: "sha256", Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703", }, }, file.NewLocation("/b/place/b").Coordinates: { { Algorithm: "sha256", Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c", }, }, }, FileContents: map[file.Coordinates]string{ file.NewLocation("/a/place/a").Coordinates: "the-contents", }, LinuxDistribution: &linux.Release{ ID: "redhat", Version: "7", VersionID: "7", IDLike: []string{ "rhel", }, }, }, Relationships: []artifact.Relationship{ { From: p1, To: p2, Type: artifact.OwnershipByFileOverlapRelationship, Data: map[string]string{ "file": "path", }, }, }, Source: source.Description{ ID: "c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0", Metadata: source.StereoscopeImageSourceMetadata{ UserInput: "user-image-input", ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0", ManifestDigest: "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", MediaType: "application/vnd.docker.distribution.manifest.v2+json", Tags: []string{ "stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b", }, Size: 38, Layers: []source.StereoscopeLayerMetadata{ { MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", Digest: "sha256:3de16c5b8659a2e8d888b8ded8427be7a5686a3c8c4e4dd30de20f362827285b", Size: 22, }, { MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", Digest: "sha256:366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703", Size: 16, }, }, RawManifest: []byte("eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJh..."), RawConfig: []byte("eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZp..."), RepoDigests: []string{}, }, }, 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", }, }, } testutil.AssertEncoderAgainstGoldenSnapshot(t, testutil.EncoderSnapshotTestConfig{ Subject: s, Format: NewFormatEncoder(), UpdateSnapshot: *updateSnapshot, PersistRedactionsInSnapshot: true, IsJSON: true, Redactor: redactor(), }, ) } func redactor(values ...string) testutil.Redactor { return testutil.NewRedactions(). WithValuesRedacted(values...). WithPatternRedactors( map[string]string{ // remove schema version (don't even show the key or value) `,?\s*"schema":\s*\{[^}]*}`: "", }, ) }