From a45e1db0adc743bd7524565da56604c3bfe83b40 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 5 Jul 2023 15:15:06 -0400 Subject: [PATCH 1/2] port to new syft source API Signed-off-by: Alex Goodman --- go.mod | 8 +- go.sum | 16 +-- grype/distro/distro_test.go | 2 +- grype/match/ignore_test.go | 16 +-- grype/pkg/context.go | 2 +- grype/pkg/package_test.go | 5 +- grype/pkg/provider_test.go | 8 +- grype/pkg/syft_provider.go | 34 +++-- grype/pkg/syft_sbom_provider_test.go | 45 +++---- grype/presenter/cyclonedx/presenter.go | 6 +- grype/presenter/cyclonedx/presenter_test.go | 18 +-- .../test_helpers.go} | 113 +++++++++++------ grype/presenter/json/presenter_test.go | 7 +- grype/presenter/models/document_test.go | 5 +- grype/presenter/models/source.go | 29 +++-- grype/presenter/models/source_test.go | 29 ++--- grype/presenter/sarif/presenter.go | 45 ++++--- grype/presenter/sarif/presenter_test.go | 116 ++++++++++-------- grype/presenter/table/presenter_test.go | 8 +- grype/presenter/template/presenter_test.go | 6 +- test/integration/match_by_image_test.go | 10 +- test/integration/utils_test.go | 11 +- 22 files changed, 308 insertions(+), 231 deletions(-) rename grype/presenter/{models/models_helpers.go => internal/test_helpers.go} (70%) diff --git a/go.mod b/go.mod index 9cbe642f23f..32aa7d09fa3 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb github.com/x-cray/logrus-prefixed-formatter v0.5.2 - golang.org/x/term v0.9.0 + golang.org/x/term v0.10.0 gopkg.in/yaml.v2 v2.4.0 gorm.io/gorm v1.23.10 ) @@ -53,7 +53,7 @@ require ( require ( github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963 - github.com/anchore/syft v0.84.1 + github.com/anchore/syft v0.84.2-0.20230705174713-cfbb9f703bd7 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b github.com/mitchellh/mapstructure v1.5.0 ) @@ -170,11 +170,11 @@ require ( go.uber.org/goleak v1.2.0 // indirect golang.org/x/crypto v0.10.0 // indirect golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b // indirect - golang.org/x/mod v0.11.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.11.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.10.0 // indirect golang.org/x/time v0.2.0 // indirect golang.org/x/tools v0.8.0 // indirect diff --git a/go.sum b/go.sum index 5b85d70446c..09fadb6961a 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,8 @@ github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963 h1:vrf2PYH77vqVJo github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963/go.mod h1:AVRyXOUP0hTz9Cb8OlD1XnwA8t4lBPfTuwPHmEUuiLc= github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e h1:zhk3ZLtomMJ750nNCE+c24PonMzoO/SeL/4uTr1L9kM= github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g= -github.com/anchore/syft v0.84.1 h1:O6V1gCSHTVbyfQq6M1qB86ui64qobZRC3h7lvKpVNWw= -github.com/anchore/syft v0.84.1/go.mod h1:dozEWcwhRawdB3ArPM2BGfZWLslZ+bDNwW+wWUwKySY= +github.com/anchore/syft v0.84.2-0.20230705174713-cfbb9f703bd7 h1:E8pdc689HTwXaHLRcmMTGi6TBukDa6oD8dQ0bJTSUm0= +github.com/anchore/syft v0.84.2-0.20230705174713-cfbb9f703bd7/go.mod h1:4ruIUJNJY2IsuUPrvUdYu8kG4ScFjGoiy/PPmgBEuTw= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -942,8 +942,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1149,8 +1149,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1159,8 +1159,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/grype/distro/distro_test.go b/grype/distro/distro_test.go index 8b6cecdfc28..faa61825f3c 100644 --- a/grype/distro/distro_test.go +++ b/grype/distro/distro_test.go @@ -227,7 +227,7 @@ func Test_NewDistroFromRelease_Coverage(t *testing.T) { for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { - s, err := source.NewFromDirectory(test.fixture) + s, err := source.NewFromDirectory(source.DirectoryConfig{Path: test.fixture}) require.NoError(t, err) resolver, err := s.FileResolver(source.SquashedScope) diff --git a/grype/match/ignore_test.go b/grype/match/ignore_test.go index 4490bf8a736..57af7c53d20 100644 --- a/grype/match/ignore_test.go +++ b/grype/match/ignore_test.go @@ -9,8 +9,8 @@ import ( grypeDb "github.com/anchore/grype/grype/db/v5" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/syft/syft/file" syftPkg "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) var ( @@ -28,7 +28,7 @@ var ( Name: "dive", Version: "0.5.2", Type: "deb", - Locations: source.NewLocationSet(source.NewLocation("/path/that/has/dive")), + Locations: file.NewLocationSet(file.NewLocation("/path/that/has/dive")), }, }, { @@ -45,7 +45,7 @@ var ( Version: "100.0.50", Language: syftPkg.Ruby, Type: syftPkg.GemPkg, - Locations: source.NewLocationSet(source.NewVirtualLocation("/real/path/with/reach", + Locations: file.NewLocationSet(file.NewVirtualLocation("/real/path/with/reach", "/virtual/path/that/has/reach")), }, }, @@ -63,7 +63,7 @@ var ( Version: "100.0.51", Language: syftPkg.Ruby, Type: syftPkg.GemPkg, - Locations: source.NewLocationSet(source.NewVirtualLocation("/real/path/with/beach", + Locations: file.NewLocationSet(file.NewVirtualLocation("/real/path/with/beach", "/virtual/path/that/has/beach")), }, }, @@ -81,7 +81,7 @@ var ( Version: "100.0.52", Language: syftPkg.Ruby, Type: syftPkg.GemPkg, - Locations: source.NewLocationSet(source.NewVirtualLocation("/real/path/with/speach", + Locations: file.NewLocationSet(file.NewVirtualLocation("/real/path/with/speach", "/virtual/path/that/has/speach")), }, }, @@ -337,9 +337,9 @@ var ( ID: pkg.ID(uuid.NewString()), Name: "a-pkg", Version: "1.0", - Locations: source.NewLocationSet( - source.NewLocation("/some/path"), - source.NewVirtualLocation("/some/path", "/some/virtual/path"), + Locations: file.NewLocationSet( + file.NewLocation("/some/path"), + file.NewVirtualLocation("/some/path", "/some/virtual/path"), ), Type: "rpm", }, diff --git a/grype/pkg/context.go b/grype/pkg/context.go index 4a2e65b56c9..5f46a6f9f9c 100644 --- a/grype/pkg/context.go +++ b/grype/pkg/context.go @@ -6,6 +6,6 @@ import ( ) type Context struct { - Source *source.Metadata + Source *source.Description Distro *linux.Release } diff --git a/grype/pkg/package_test.go b/grype/pkg/package_test.go index 2e4a5703e08..b863d47ce35 100644 --- a/grype/pkg/package_test.go +++ b/grype/pkg/package_test.go @@ -14,7 +14,6 @@ import ( "github.com/anchore/syft/syft/file" syftFile "github.com/anchore/syft/syft/file" syftPkg "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func TestNew(t *testing.T) { @@ -550,8 +549,8 @@ func TestFromCollection_DoesNotPanic(t *testing.T) { examplePackage := syftPkg.Package{ Name: "test", Version: "1.2.3", - Locations: source.NewLocationSet( - source.NewLocation("/test-path"), + Locations: file.NewLocationSet( + file.NewLocation("/test-path"), ), Type: syftPkg.NpmPkg, } diff --git a/grype/pkg/provider_test.go b/grype/pkg/provider_test.go index a9aaf1f1899..34dd94432c5 100644 --- a/grype/pkg/provider_test.go +++ b/grype/pkg/provider_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg/cataloger" - "github.com/anchore/syft/syft/source" ) func TestProviderLocationExcludes(t *testing.T) { @@ -158,10 +158,10 @@ func Test_filterPackageExclusions(t *testing.T) { t.Run(test.name, func(t *testing.T) { var packages []Package for _, pkg := range test.locations { - locations := source.NewLocationSet() + locations := file.NewLocationSet() for _, l := range pkg { locations.Add( - source.NewVirtualLocation(l, l), + file.NewVirtualLocation(l, l), ) } packages = append(packages, Package{Locations: locations}) @@ -221,7 +221,7 @@ func Test_matchesLocation(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - matches, err := locationMatches(source.NewVirtualLocation(test.realPath, test.virtualPath), test.match) + matches, err := locationMatches(file.NewVirtualLocation(test.realPath, test.virtualPath), test.match) assert.NoError(t, err) assert.Equal(t, test.expected, matches) }) diff --git a/grype/pkg/syft_provider.go b/grype/pkg/syft_provider.go index 8b541ba2a60..caf9ca74d14 100644 --- a/grype/pkg/syft_provider.go +++ b/grype/pkg/syft_provider.go @@ -1,6 +1,7 @@ package pkg import ( + "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" @@ -11,16 +12,33 @@ func syftProvider(userInput string, config ProviderConfig) ([]Package, Context, return nil, Context{}, nil, errDoesNotProvide } - sourceInput, err := source.ParseInputWithName(userInput, config.Platform, config.Name, config.DefaultImagePullSource) + detection, err := source.Detect(userInput, source.DetectConfig{ + DefaultImageSource: config.DefaultImagePullSource, + }) if err != nil { return nil, Context{}, nil, err } - src, cleanup, err := source.New(*sourceInput, config.RegistryOptions, config.Exclusions) - if err != nil { - return nil, Context{}, nil, err + var platform *image.Platform + if config.Platform != "" { + platform, err = image.NewPlatform(config.Platform) + if err != nil { + return nil, Context{}, nil, err + } } - defer cleanup() + + src, err := detection.NewSource(source.DetectionSourceConfig{ + Alias: source.Alias{ + Name: config.Name, + }, + RegistryOptions: config.RegistryOptions, + Platform: platform, + Exclude: source.ExcludeConfig{ + Paths: config.Exclusions, + }, + }) + + defer src.Close() catalog, relationships, theDistro, err := syft.CatalogPackages(src, config.CatalogingOptions) if err != nil { @@ -29,14 +47,16 @@ func syftProvider(userInput string, config ProviderConfig) ([]Package, Context, catalog = removePackagesByOverlap(catalog, relationships) + srcDescription := src.Describe() + packages := FromCollection(catalog, config.SynthesisConfig) context := Context{ - Source: &src.Metadata, + Source: &srcDescription, Distro: theDistro, } sbom := &sbom.SBOM{ - Source: src.Metadata, + Source: srcDescription, Relationships: relationships, Artifacts: sbom.Artifacts{ Packages: catalog, diff --git a/grype/pkg/syft_sbom_provider_test.go b/grype/pkg/syft_sbom_provider_test.go index 774dda2a874..4d1b0a12164 100644 --- a/grype/pkg/syft_sbom_provider_test.go +++ b/grype/pkg/syft_sbom_provider_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/source" ) @@ -26,8 +27,8 @@ func TestParseSyftJSON(t *testing.T) { { Name: "alpine-baselayout", Version: "3.2.0-r6", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/lib/apk/db/installed", FileSystemID: "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759", }), @@ -50,8 +51,8 @@ func TestParseSyftJSON(t *testing.T) { { Name: "fake", Version: "1.2.0", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/lib/apk/db/installed", FileSystemID: "sha256:93cf4cfb673c7e16a9e74f731d6767b70b92a0b7c9f59d06efd72fbff535371c", }), @@ -76,8 +77,8 @@ func TestParseSyftJSON(t *testing.T) { { Name: "gmp", Version: "6.2.0-r0", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/lib/apk/db/installed", FileSystemID: "sha256:93cf4cfb673c7e16a9e74f731d6767b70b92a0b7c9f59d06efd72fbff535371c", }), @@ -101,11 +102,10 @@ func TestParseSyftJSON(t *testing.T) { }, }, Context: Context{ - Source: &source.Metadata{ - Scheme: source.ImageScheme, - ImageMetadata: source.ImageMetadata{ + Source: &source.Description{ + Metadata: source.StereoscopeImageSourceMetadata{ UserInput: "alpine:fake", - Layers: []source.LayerMetadata{ + Layers: []source.StereoscopeLayerMetadata{ { MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", Digest: "sha256:50644c29ef5a27c9a40c393a73ece2479de78325cae7d762ef3cdc19bf42dd0a", @@ -120,7 +120,6 @@ func TestParseSyftJSON(t *testing.T) { "alpine:fake", }, }, - Path: "", }, Distro: &linux.Release{ Name: "alpine", @@ -138,8 +137,12 @@ func TestParseSyftJSON(t *testing.T) { t.Fatalf("unable to parse: %+v", err) } - context.Source.ImageMetadata.RawConfig = nil - context.Source.ImageMetadata.RawManifest = nil + if m, ok := context.Source.Metadata.(source.StereoscopeImageSourceMetadata); ok { + m.RawConfig = nil + m.RawManifest = nil + + context.Source.Metadata = m + } for _, d := range deep.Equal(test.Packages, pkgs) { if strings.Contains(d, ".ID: ") { @@ -179,8 +182,8 @@ var springImageTestCase = struct { { Name: "charsets", Version: "", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar", FileSystemID: "sha256:a1a6ceadb701ab4e6c93b243dc2a0daedc8cee23a24203845ecccd5784cd1393", }), @@ -199,8 +202,8 @@ var springImageTestCase = struct { { Name: "tomcat-embed-el", Version: "9.0.27", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/app/libs/tomcat-embed-el-9.0.27.jar", FileSystemID: "sha256:89504f083d3f15322f97ae240df44650203f24427860db1b3d32e66dd05940e4", }), @@ -218,11 +221,10 @@ var springImageTestCase = struct { }, }, Context: Context{ - Source: &source.Metadata{ - Scheme: source.ImageScheme, - ImageMetadata: source.ImageMetadata{ + Source: &source.Description{ + Metadata: source.StereoscopeImageSourceMetadata{ UserInput: "springio/gs-spring-boot-docker:latest", - Layers: []source.LayerMetadata{ + Layers: []source.StereoscopeLayerMetadata{ { MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", Digest: "sha256:42a3027eaac150d2b8f516100921f4bd83b3dbc20bfe64124f686c072b49c602", @@ -238,7 +240,6 @@ var springImageTestCase = struct { }, RepoDigests: []string{"springio/gs-spring-boot-docker@sha256:39c2ffc784f5f34862e22c1f2ccdbcb62430736114c13f60111eabdb79decb08"}, }, - Path: "", }, Distro: &linux.Release{ Name: "debian", diff --git a/grype/presenter/cyclonedx/presenter.go b/grype/presenter/cyclonedx/presenter.go index 1069d9106a1..54b1a957b3f 100644 --- a/grype/presenter/cyclonedx/presenter.go +++ b/grype/presenter/cyclonedx/presenter.go @@ -20,7 +20,7 @@ import ( type Presenter struct { results match.Matches packages []pkg.Package - srcMetadata *source.Metadata + src *source.Description metadataProvider vulnerability.MetadataProvider format cyclonedx.BOMFileFormat sbom *sbom.SBOM @@ -32,7 +32,7 @@ func NewJSONPresenter(pb models.PresenterConfig) *Presenter { results: pb.Matches, packages: pb.Packages, metadataProvider: pb.MetadataProvider, - srcMetadata: pb.Context.Source, + src: pb.Context.Source, sbom: pb.SBOM, format: cyclonedx.BOMFileFormatJSON, } @@ -44,7 +44,7 @@ func NewXMLPresenter(pb models.PresenterConfig) *Presenter { results: pb.Matches, packages: pb.Packages, metadataProvider: pb.MetadataProvider, - srcMetadata: pb.Context.Source, + src: pb.Context.Source, sbom: pb.SBOM, format: cyclonedx.BOMFileFormatXML, } diff --git a/grype/presenter/cyclonedx/presenter_test.go b/grype/presenter/cyclonedx/presenter_test.go index bd45a9cc888..c0e4debb900 100644 --- a/grype/presenter/cyclonedx/presenter_test.go +++ b/grype/presenter/cyclonedx/presenter_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/go-testutils" + "github.com/anchore/grype/grype/presenter/internal" "github.com/anchore/grype/grype/presenter/models" - "github.com/anchore/syft/syft/source" ) var update = flag.Bool("update", false, "update the *.golden files for cyclonedx presenters") @@ -17,8 +17,8 @@ var update = flag.Bool("update", false, "update the *.golden files for cyclonedx func TestCycloneDxPresenterImage(t *testing.T) { var buffer bytes.Buffer - matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, source.ImageScheme) - sbom := models.SBOMFromPackages(t, packages) + matches, packages, context, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.ImageSource) + sbom := internal.SBOMFromPackages(t, packages) pb := models.PresenterConfig{ Matches: matches, Packages: packages, @@ -42,16 +42,16 @@ func TestCycloneDxPresenterImage(t *testing.T) { var expected = testutils.GetGoldenFileContents(t) // remove dynamic values, which are tested independently - actual = models.Redact(actual) - expected = models.Redact(expected) + actual = internal.Redact(actual) + expected = internal.Redact(expected) require.JSONEq(t, string(expected), string(actual)) } func TestCycloneDxPresenterDir(t *testing.T) { var buffer bytes.Buffer - matches, packages, ctx, metadataProvider, _, _ := models.GenerateAnalysis(t, source.DirectoryScheme) - sbom := models.SBOMFromPackages(t, packages) + matches, packages, ctx, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.DirectorySource) + sbom := internal.SBOMFromPackages(t, packages) pb := models.PresenterConfig{ Matches: matches, Packages: packages, @@ -76,8 +76,8 @@ func TestCycloneDxPresenterDir(t *testing.T) { var expected = testutils.GetGoldenFileContents(t) // remove dynamic values, which are tested independently - actual = models.Redact(actual) - expected = models.Redact(expected) + actual = internal.Redact(actual) + expected = internal.Redact(expected) require.JSONEq(t, string(expected), string(actual)) } diff --git a/grype/presenter/models/models_helpers.go b/grype/presenter/internal/test_helpers.go similarity index 70% rename from grype/presenter/models/models_helpers.go rename to grype/presenter/internal/test_helpers.go index d8a745a83c2..c5723697b31 100644 --- a/grype/presenter/models/models_helpers.go +++ b/grype/presenter/internal/test_helpers.go @@ -1,4 +1,4 @@ -package models +package internal import ( "regexp" @@ -9,6 +9,7 @@ import ( grypeDb "github.com/anchore/grype/grype/db/v5" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/grype/grype/vulnerability" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/syft/artifact" @@ -20,17 +21,25 @@ import ( syftSource "github.com/anchore/syft/syft/source" ) -func GenerateAnalysis(t *testing.T, scheme syftSource.Scheme) (match.Matches, []pkg.Package, pkg.Context, vulnerability.MetadataProvider, interface{}, interface{}) { +const ( + DirectorySource SyftSource = "directory" + ImageSource SyftSource = "image" + FileSource SyftSource = "file" +) + +type SyftSource string + +func GenerateAnalysis(t *testing.T, scheme SyftSource) (match.Matches, []pkg.Package, pkg.Context, vulnerability.MetadataProvider, interface{}, interface{}) { t.Helper() packages := generatePackages(t) matches := generateMatches(t, packages[0], packages[1]) context := generateContext(t, scheme) - return matches, packages, context, NewMetadataMock(), nil, nil + return matches, packages, context, models.NewMetadataMock(), nil, nil } -func GenerateAnalysisWithIgnoredMatches(t *testing.T, scheme syftSource.Scheme) (match.Matches, []match.IgnoredMatch, []pkg.Package, pkg.Context, vulnerability.MetadataProvider, interface{}, interface{}) { +func GenerateAnalysisWithIgnoredMatches(t *testing.T, scheme SyftSource) (match.Matches, []match.IgnoredMatch, []pkg.Package, pkg.Context, vulnerability.MetadataProvider, interface{}, interface{}) { t.Helper() packages := generatePackages(t) @@ -38,7 +47,7 @@ func GenerateAnalysisWithIgnoredMatches(t *testing.T, scheme syftSource.Scheme) ignoredMatches := generateIgnoredMatches(t, packages[1]) context := generateContext(t, scheme) - return matches, ignoredMatches, packages, context, NewMetadataMock(), nil, nil + return matches, ignoredMatches, packages, context, models.NewMetadataMock(), nil, nil } func SBOMFromPackages(t *testing.T, packages []pkg.Package) *sbom.SBOM { @@ -260,59 +269,83 @@ func generatePackages(t *testing.T) []pkg.Package { return updatedPkgs } -func generateContext(t *testing.T, scheme syftSource.Scheme) pkg.Context { - var src syftSource.Source - img := image.Image{ - Metadata: image.Metadata{ - ID: "sha256:ab5608d634db2716a297adbfa6a5dd5d8f8f5a7d0cab73649ea7fbb8c8da544f", - ManifestDigest: "sha256:ca738abb87a8d58f112d3400ebb079b61ceae7dc290beb34bda735be4b1941d5", - MediaType: "application/vnd.docker.distribution.manifest.v2+json", - Size: 65, - }, - Layers: []*image.Layer{ - { - Metadata: image.LayerMetadata{ - Digest: "sha256:ca738abb87a8d58f112d3400ebb079b61ceae7dc290beb34bda735be4b1941d5", - MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", - Size: 22, - }, +func generateContext(t *testing.T, scheme SyftSource) pkg.Context { + var ( + src syftSource.Source + desc syftSource.Description + ) + + switch scheme { + case FileSource: + var err error + src, err = syftSource.NewFromFile(syftSource.FileConfig{ + Path: "user-input", + }) + if err != nil { + t.Fatalf("failed to generate mock file source from mock image: %+v", err) + } + desc = src.Describe() + case ImageSource: + img := image.Image{ + Metadata: image.Metadata{ + ID: "sha256:ab5608d634db2716a297adbfa6a5dd5d8f8f5a7d0cab73649ea7fbb8c8da544f", + ManifestDigest: "sha256:ca738abb87a8d58f112d3400ebb079b61ceae7dc290beb34bda735be4b1941d5", + MediaType: "application/vnd.docker.distribution.manifest.v2+json", + Size: 65, }, - { - Metadata: image.LayerMetadata{ - Digest: "sha256:a05cd9ebf88af96450f1e25367281ab232ac0645f314124fe01af759b93f3006", - MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", - Size: 16, + Layers: []*image.Layer{ + { + Metadata: image.LayerMetadata{ + Digest: "sha256:ca738abb87a8d58f112d3400ebb079b61ceae7dc290beb34bda735be4b1941d5", + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Size: 22, + }, }, - }, - { - Metadata: image.LayerMetadata{ - Digest: "sha256:ab5608d634db2716a297adbfa6a5dd5d8f8f5a7d0cab73649ea7fbb8c8da544f", - MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", - Size: 27, + { + Metadata: image.LayerMetadata{ + Digest: "sha256:a05cd9ebf88af96450f1e25367281ab232ac0645f314124fe01af759b93f3006", + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Size: 16, + }, + }, + { + Metadata: image.LayerMetadata{ + Digest: "sha256:ab5608d634db2716a297adbfa6a5dd5d8f8f5a7d0cab73649ea7fbb8c8da544f", + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Size: 27, + }, }, }, - }, - } + } - switch scheme { - case syftSource.ImageScheme: var err error - src, err = syftSource.NewFromImage(&img, "user-input") + src, err = syftSource.NewFromStereoscopeImageObject(&img, "user-input", nil) if err != nil { t.Fatalf("failed to generate mock image source from mock image: %+v", err) } - case syftSource.DirectoryScheme: + desc = src.Describe() + case DirectorySource: + // note: the dir must exist for the source to be created + d := t.TempDir() var err error - src, err = syftSource.NewFromDirectory("/some/path") + src, err = syftSource.NewFromDirectory(syftSource.DirectoryConfig{ + Path: d, + }) + if err != nil { t.Fatalf("failed to generate mock directory source from mock dir: %+v", err) } + desc = src.Describe() + if m, ok := desc.Metadata.(syftSource.DirectorySourceMetadata); ok { + m.Path = "/some/path" + desc.Metadata = m + } default: t.Fatalf("unknown scheme: %s", scheme) } return pkg.Context{ - Source: &src.Metadata, + Source: &desc, Distro: &linux.Release{ Name: "centos", IDLike: []string{ diff --git a/grype/presenter/json/presenter_test.go b/grype/presenter/json/presenter_test.go index 12a471c1fc1..7806ba60ef1 100644 --- a/grype/presenter/json/presenter_test.go +++ b/grype/presenter/json/presenter_test.go @@ -11,6 +11,7 @@ import ( "github.com/anchore/go-testutils" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/presenter/internal" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/source" @@ -21,7 +22,7 @@ var timestampRegexp = regexp.MustCompile(`"timestamp":\s*"[^"]+"`) func TestJsonImgsPresenter(t *testing.T) { var buffer bytes.Buffer - matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, source.ImageScheme) + matches, packages, context, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.ImageSource) pb := models.PresenterConfig{ Matches: matches, @@ -54,7 +55,7 @@ func TestJsonImgsPresenter(t *testing.T) { func TestJsonDirsPresenter(t *testing.T) { var buffer bytes.Buffer - matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, source.DirectoryScheme) + matches, packages, context, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.DirectorySource) pb := models.PresenterConfig{ Matches: matches, @@ -91,7 +92,7 @@ func TestEmptyJsonPresenter(t *testing.T) { matches := match.NewMatches() ctx := pkg.Context{ - Source: &source.Metadata{}, + Source: &source.Description{}, Distro: &linux.Release{ ID: "centos", IDLike: []string{"rhel"}, diff --git a/grype/presenter/models/document_test.go b/grype/presenter/models/document_test.go index dc0031f4881..588a074cf89 100644 --- a/grype/presenter/models/document_test.go +++ b/grype/presenter/models/document_test.go @@ -72,9 +72,8 @@ func TestPackagesAreSorted(t *testing.T) { packages := []pkg.Package{pkg1, pkg2} ctx := pkg.Context{ - Source: &syftSource.Metadata{ - Scheme: syftSource.DirectoryScheme, - ImageMetadata: syftSource.ImageMetadata{}, + Source: &syftSource.Description{ + Metadata: syftSource.DirectorySourceMetadata{}, }, Distro: &linux.Release{ ID: "centos", diff --git a/grype/presenter/models/source.go b/grype/presenter/models/source.go index 8648770c40d..bdfecbe3c52 100644 --- a/grype/presenter/models/source.go +++ b/grype/presenter/models/source.go @@ -12,39 +12,38 @@ type source struct { } // newSource creates a new source object to be represented into JSON. -func newSource(src syftSource.Metadata) (source, error) { - switch src.Scheme { - case syftSource.ImageScheme: - metadata := src.ImageMetadata +func newSource(src syftSource.Description) (source, error) { + switch m := src.Metadata.(type) { + case syftSource.StereoscopeImageSourceMetadata: // ensure that empty collections are not shown as null - if metadata.RepoDigests == nil { - metadata.RepoDigests = []string{} + if m.RepoDigests == nil { + m.RepoDigests = []string{} } - if metadata.Tags == nil { - metadata.Tags = []string{} + if m.Tags == nil { + m.Tags = []string{} } return source{ Type: "image", - Target: metadata, + Target: m, }, nil - case syftSource.DirectoryScheme: + case syftSource.DirectorySourceMetadata: return source{ Type: "directory", - Target: src.Path, + Target: m.Path, }, nil - case syftSource.FileScheme: + case syftSource.FileSourceMetadata: return source{ Type: "file", - Target: src.Path, + Target: m.Path, }, nil - case "": + case nil: // we may be showing results from a input source that does not support source information return source{ Type: "unknown", Target: "unknown", }, nil default: - return source{}, fmt.Errorf("unsupported source: %q", src.Scheme) + return source{}, fmt.Errorf("unsupported source: %T", src.Metadata) } } diff --git a/grype/presenter/models/source_test.go b/grype/presenter/models/source_test.go index b1e33a5b132..fc24bef2b26 100644 --- a/grype/presenter/models/source_test.go +++ b/grype/presenter/models/source_test.go @@ -12,14 +12,13 @@ import ( func TestNewSource(t *testing.T) { testCases := []struct { name string - metadata syftSource.Metadata + metadata syftSource.Description expected source }{ { name: "image", - metadata: syftSource.Metadata{ - Scheme: syftSource.ImageScheme, - ImageMetadata: syftSource.ImageMetadata{ + metadata: syftSource.Description{ + Metadata: syftSource.StereoscopeImageSourceMetadata{ UserInput: "abc", ID: "def", ManifestDigest: "abcdef", @@ -28,7 +27,7 @@ func TestNewSource(t *testing.T) { }, expected: source{ Type: "image", - Target: syftSource.ImageMetadata{ + Target: syftSource.StereoscopeImageSourceMetadata{ UserInput: "abc", ID: "def", ManifestDigest: "abcdef", @@ -40,9 +39,10 @@ func TestNewSource(t *testing.T) { }, { name: "directory", - metadata: syftSource.Metadata{ - Scheme: syftSource.DirectoryScheme, - Path: "/foo/bar", + metadata: syftSource.Description{ + Metadata: syftSource.DirectorySourceMetadata{ + Path: "/foo/bar", + }, }, expected: source{ Type: "directory", @@ -51,9 +51,10 @@ func TestNewSource(t *testing.T) { }, { name: "file", - metadata: syftSource.Metadata{ - Scheme: syftSource.FileScheme, - Path: "/foo/bar/test.zip", + metadata: syftSource.Description{ + Metadata: syftSource.FileSourceMetadata{ + Path: "/foo/bar/test.zip", + }, }, expected: source{ Type: "file", @@ -62,18 +63,12 @@ func TestNewSource(t *testing.T) { }, } - var testedSchemes []syftSource.Scheme - for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { actual, err := newSource(testCase.metadata) require.NoError(t, err) assert.Equal(t, testCase.expected, actual) - testedSchemes = append(testedSchemes, testCase.metadata.Scheme) }) } - - // Ensure we have test coverage for all possible syftSource.Scheme values. - assert.ElementsMatchf(t, syftSource.AllSchemes, testedSchemes, "not all scheme values are being tested") } diff --git a/grype/presenter/sarif/presenter.go b/grype/presenter/sarif/presenter.go index 63a899df3a3..95eb7c2ff15 100644 --- a/grype/presenter/sarif/presenter.go +++ b/grype/presenter/sarif/presenter.go @@ -22,7 +22,7 @@ import ( type Presenter struct { results match.Matches packages []pkg.Package - srcMetadata *source.Metadata + src *source.Description metadataProvider vulnerability.MetadataProvider } @@ -32,7 +32,7 @@ func NewPresenter(pb models.PresenterConfig) *Presenter { results: pb.Matches, packages: pb.Packages, metadataProvider: pb.MetadataProvider, - srcMetadata: pb.Context.Source, + src: pb.Context.Source, } } @@ -163,10 +163,19 @@ func (pres *Presenter) packagePath(p pkg.Package) string { // inputPath returns a friendlier relative path or absolute path depending on the input, not prefixed by . or ./ func (pres *Presenter) inputPath() string { - if pres.srcMetadata == nil { + if pres.src == nil { return "" } - inputPath := strings.TrimPrefix(pres.srcMetadata.Path, "./") + var inputPath string + switch m := pres.src.Metadata.(type) { + case source.FileSourceMetadata: + inputPath = m.Path + case source.DirectorySourceMetadata: + inputPath = m.Path + default: + return "" + } + inputPath = strings.TrimPrefix(inputPath, "./") if inputPath == "." { return "" } @@ -182,13 +191,17 @@ func (pres *Presenter) locationPath(l file.Location) string { in := pres.inputPath() path = strings.TrimPrefix(path, "./") // trimmed off any ./ and accounted for dir:. for both path and input path - if pres.srcMetadata != nil && pres.srcMetadata.Scheme == source.DirectoryScheme { - if filepath.IsAbs(path) || in == "" { - return path + if pres.src != nil { + _, ok := pres.src.Metadata.(source.DirectorySourceMetadata) + if ok { + if filepath.IsAbs(path) || in == "" { + return path + } + // return a path relative to the cwd, if it's not absolute + return fmt.Sprintf("%s/%s", in, path) } - // return a path relative to the cwd, if it's not absolute - return fmt.Sprintf("%s/%s", in, path) } + return path } @@ -198,9 +211,9 @@ func (pres *Presenter) locations(m match.Match) []*sarif.Location { var logicalLocations []*sarif.LogicalLocation - switch pres.srcMetadata.Scheme { - case source.ImageScheme: - img := pres.srcMetadata.ImageMetadata.UserInput + switch metadata := pres.src.Metadata.(type) { + case source.StereoscopeImageSourceMetadata: + img := metadata.UserInput locations := m.Package.Locations.ToSlice() for _, l := range locations { trimmedPath := strings.TrimPrefix(pres.locationPath(l), "/") @@ -215,15 +228,15 @@ func (pres *Presenter) locations(m match.Match) []*sarif.Location { // TODO we could add configuration to specify the prefix, a user might want to specify an image name and architecture // in the case of multiple vuln scans, for example physicalLocation = fmt.Sprintf("image/%s", physicalLocation) - case source.FileScheme: + case source.FileSourceMetadata: locations := m.Package.Locations.ToSlice() for _, l := range locations { logicalLocations = append(logicalLocations, &sarif.LogicalLocation{ - FullyQualifiedName: sp(fmt.Sprintf("%s:/%s", pres.srcMetadata.Path, pres.locationPath(l))), + FullyQualifiedName: sp(fmt.Sprintf("%s:/%s", metadata.Path, pres.locationPath(l))), Name: sp(l.RealPath), }) } - case source.DirectoryScheme: + case source.DirectorySourceMetadata: // DirectoryScheme is already handled, with input prepended if needed } @@ -399,7 +412,7 @@ func (pres *Presenter) resultMessage(m match.Match) sarif.Message { path := pres.packagePath(m.Package) message := fmt.Sprintf("The path %s reports %s at version %s ", path, m.Package.Name, m.Package.Version) - if pres.srcMetadata.Scheme == source.DirectoryScheme { + if _, ok := pres.src.Metadata.(source.DirectorySourceMetadata); ok { message = fmt.Sprintf("%s which would result in a vulnerable (%s) package installed", message, m.Package.Type) } else { message = fmt.Sprintf("%s which is a vulnerable (%s) package installed in the container", message, m.Package.Type) diff --git a/grype/presenter/sarif/presenter_test.go b/grype/presenter/sarif/presenter_test.go index a83fbc7c1e8..f770f5691a6 100644 --- a/grype/presenter/sarif/presenter_test.go +++ b/grype/presenter/sarif/presenter_test.go @@ -10,8 +10,10 @@ import ( "github.com/anchore/go-testutils" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/presenter/internal" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" ) @@ -20,15 +22,15 @@ var update = flag.Bool("update", false, "update .golden files for sarif presente func TestSarifPresenter(t *testing.T) { tests := []struct { name string - scheme source.Scheme + scheme internal.SyftSource }{ { name: "directory", - scheme: source.DirectoryScheme, + scheme: internal.DirectorySource, }, { name: "image", - scheme: source.ImageScheme, + scheme: internal.ImageSource, }, } @@ -36,7 +38,7 @@ func TestSarifPresenter(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { var buffer bytes.Buffer - matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, tc.scheme) + matches, packages, context, metadataProvider, _, _ := internal.GenerateAnalysis(t, tc.scheme) pb := models.PresenterConfig{ Matches: matches, @@ -57,8 +59,8 @@ func TestSarifPresenter(t *testing.T) { } var expected = testutils.GetGoldenFileContents(t) - actual = models.Redact(actual) - expected = models.Redact(expected) + actual = internal.Redact(actual) + expected = internal.Redact(expected) if !bytes.Equal(expected, actual) { assert.JSONEq(t, string(expected), string(actual)) @@ -70,83 +72,92 @@ func TestSarifPresenter(t *testing.T) { func Test_locationPath(t *testing.T) { tests := []struct { name string - path string - scheme source.Scheme + metadata any real string virtual string expected string }{ { - name: "dir:.", - scheme: source.DirectoryScheme, - path: ".", + name: "dir:.", + metadata: source.DirectorySourceMetadata{ + Path: ".", + }, real: "/home/usr/file", virtual: "file", expected: "file", }, { - name: "dir:./", - scheme: source.DirectoryScheme, - path: "./", + name: "dir:./", + metadata: source.DirectorySourceMetadata{ + Path: "./", + }, real: "/home/usr/file", virtual: "file", expected: "file", }, { - name: "dir:./someplace", - scheme: source.DirectoryScheme, - path: "./someplace", + name: "dir:./someplace", + metadata: source.DirectorySourceMetadata{ + Path: "./someplace", + }, real: "/home/usr/file", virtual: "file", expected: "someplace/file", }, { - name: "dir:/someplace", - scheme: source.DirectoryScheme, - path: "/someplace", + name: "dir:/someplace", + metadata: source.DirectorySourceMetadata{ + Path: "/someplace", + }, real: "file", expected: "/someplace/file", }, { - name: "dir:/someplace symlink", - scheme: source.DirectoryScheme, - path: "/someplace", + name: "dir:/someplace symlink", + metadata: source.DirectorySourceMetadata{ + Path: "/someplace", + }, real: "/someplace/usr/file", virtual: "file", expected: "/someplace/file", }, { - name: "dir:/someplace absolute", - scheme: source.DirectoryScheme, - path: "/someplace", + name: "dir:/someplace absolute", + metadata: source.DirectorySourceMetadata{ + Path: "/someplace", + }, real: "/usr/file", expected: "/usr/file", }, { - name: "file:/someplace/file", - scheme: source.FileScheme, - path: "/someplace/file", + name: "file:/someplace/file", + metadata: source.FileSourceMetadata{ + Path: "/someplace/file", + }, real: "/usr/file", expected: "/usr/file", }, { - name: "file:/someplace/file relative", - scheme: source.FileScheme, - path: "/someplace/file", + name: "file:/someplace/file relative", + metadata: source.FileSourceMetadata{ + Path: "/someplace/file", + }, real: "file", expected: "file", }, { - name: "image", - scheme: source.ImageScheme, - path: "alpine:latest", + name: "image", + metadata: source.StereoscopeImageSourceMetadata{ + UserInput: "alpine:latest", + }, real: "/etc/file", expected: "/etc/file", }, { - name: "image symlink", - scheme: source.ImageScheme, - path: "alpine:latest", + name: "image symlink", + metadata: source.StereoscopeImageSourceMetadata{ + UserInput: "alpine:latest", + }, real: "/etc/elsewhere/file", virtual: "/etc/file", expected: "/etc/file", @@ -155,15 +166,14 @@ func Test_locationPath(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - pres := createDirPresenter(t, test.path) - pres.srcMetadata = &source.Metadata{ - Scheme: test.scheme, - Path: test.path, + pres := createDirPresenter(t) + pres.src = &source.Description{ + Metadata: test.metadata, } path := pres.packagePath(pkg.Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation(test.real, test.virtual), + Locations: file.NewLocationSet( + file.NewVirtualLocation(test.real, test.virtual), ), }) @@ -172,19 +182,21 @@ func Test_locationPath(t *testing.T) { } } -func createDirPresenter(t *testing.T, path string) *Presenter { - matches, packages, _, metadataProvider, _, _ := models.GenerateAnalysis(t, source.DirectoryScheme) - s, err := source.NewFromDirectory(path) +func createDirPresenter(t *testing.T) *Presenter { + matches, packages, _, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.DirectorySource) + d := t.TempDir() + s, err := source.NewFromDirectory(source.DirectoryConfig{Path: d}) if err != nil { t.Fatal(err) } + desc := s.Describe() pb := models.PresenterConfig{ Matches: matches, Packages: packages, MetadataProvider: metadataProvider, Context: pkg.Context{ - Source: &s.Metadata, + Source: &desc, }, } @@ -196,12 +208,12 @@ func createDirPresenter(t *testing.T, path string) *Presenter { func TestToSarifReport(t *testing.T) { tt := []struct { name string - scheme source.Scheme + scheme internal.SyftSource locations map[string]string }{ { name: "directory", - scheme: source.DirectoryScheme, + scheme: internal.DirectorySource, locations: map[string]string{ "CVE-1999-0001-package-1": "/some/path/somefile-1.txt", "CVE-1999-0002-package-2": "/some/path/somefile-2.txt", @@ -209,7 +221,7 @@ func TestToSarifReport(t *testing.T) { }, { name: "image", - scheme: source.ImageScheme, + scheme: internal.ImageSource, locations: map[string]string{ "CVE-1999-0001-package-1": "image/somefile-1.txt", "CVE-1999-0002-package-2": "image/somefile-2.txt", @@ -222,7 +234,7 @@ func TestToSarifReport(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - matches, packages, context, metadataProvider, _, _ := models.GenerateAnalysis(t, tc.scheme) + matches, packages, context, metadataProvider, _, _ := internal.GenerateAnalysis(t, tc.scheme) pb := models.PresenterConfig{ Matches: matches, diff --git a/grype/presenter/table/presenter_test.go b/grype/presenter/table/presenter_test.go index cf3682f3ea4..ce44ad3b493 100644 --- a/grype/presenter/table/presenter_test.go +++ b/grype/presenter/table/presenter_test.go @@ -12,10 +12,10 @@ import ( "github.com/anchore/go-testutils" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/presenter/internal" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/grype/grype/vulnerability" syftPkg "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) var update = flag.Bool("update", false, "update the *.golden files for table presenters") @@ -76,7 +76,7 @@ func TestCreateRow(t *testing.T) { func TestTablePresenter(t *testing.T) { var buffer bytes.Buffer - matches, packages, _, metadataProvider, _, _ := models.GenerateAnalysis(t, source.ImageScheme) + matches, packages, _, metadataProvider, _, _ := internal.GenerateAnalysis(t, internal.ImageSource) pb := models.PresenterConfig{ Matches: matches, @@ -174,7 +174,7 @@ func TestRemoveDuplicateRows(t *testing.T) { func TestHidesIgnoredMatches(t *testing.T) { var buffer bytes.Buffer - matches, ignoredMatches, packages, _, metadataProvider, _, _ := models.GenerateAnalysisWithIgnoredMatches(t, source.ImageScheme) + matches, ignoredMatches, packages, _, metadataProvider, _, _ := internal.GenerateAnalysisWithIgnoredMatches(t, internal.ImageSource) pb := models.PresenterConfig{ Matches: matches, @@ -205,7 +205,7 @@ func TestHidesIgnoredMatches(t *testing.T) { func TestDisplaysIgnoredMatches(t *testing.T) { var buffer bytes.Buffer - matches, ignoredMatches, packages, _, metadataProvider, _, _ := models.GenerateAnalysisWithIgnoredMatches(t, source.ImageScheme) + matches, ignoredMatches, packages, _, metadataProvider, _, _ := internal.GenerateAnalysisWithIgnoredMatches(t, internal.ImageSource) pb := models.PresenterConfig{ Matches: matches, diff --git a/grype/presenter/template/presenter_test.go b/grype/presenter/template/presenter_test.go index 9acaf6cd59e..502f8ca3f75 100644 --- a/grype/presenter/template/presenter_test.go +++ b/grype/presenter/template/presenter_test.go @@ -11,14 +11,14 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/go-testutils" + "github.com/anchore/grype/grype/presenter/internal" "github.com/anchore/grype/grype/presenter/models" - "github.com/anchore/syft/syft/source" ) var update = flag.Bool("update", false, "update the *.golden files for template presenters") func TestPresenter_Present(t *testing.T) { - matches, packages, context, metadataProvider, appConfig, dbStatus := models.GenerateAnalysis(t, source.ImageScheme) + matches, packages, context, metadataProvider, appConfig, dbStatus := internal.GenerateAnalysis(t, internal.ImageSource) workingDirectory, err := os.Getwd() if err != nil { @@ -53,7 +53,7 @@ func TestPresenter_Present(t *testing.T) { } func TestPresenter_SprigDate_Fails(t *testing.T) { - matches, packages, context, metadataProvider, appConfig, dbStatus := models.GenerateAnalysis(t, source.ImageScheme) + matches, packages, context, metadataProvider, appConfig, dbStatus := internal.GenerateAnalysis(t, internal.ImageSource) workingDirectory, err := os.Getwd() require.NoError(t, err) diff --git a/test/integration/match_by_image_test.go b/test/integration/match_by_image_test.go index 75f84cd84ac..1516e60f723 100644 --- a/test/integration/match_by_image_test.go +++ b/test/integration/match_by_image_test.go @@ -606,13 +606,15 @@ func TestMatchByImage(t *testing.T) { userImage := "docker-archive:" + tarPath - sourceInput, err := source.ParseInput(userImage, "") + detection, err := source.Detect(userImage, source.DetectConfig{}) require.NoError(t, err) // this is purely done to help setup mocks - theSource, cleanup, err := source.New(*sourceInput, nil, nil) + theSource, err := detection.NewSource(source.DetectionSourceConfig{}) require.NoError(t, err) - defer cleanup() + t.Cleanup(func() { + require.NoError(t, theSource.Close()) + }) // TODO: relationships are not verified at this time config := cataloger.DefaultConfig() @@ -645,7 +647,7 @@ func TestMatchByImage(t *testing.T) { } // build expected matches from what's discovered from the catalog - expectedMatches := test.expectedFn(*theSource, collection, theStore) + expectedMatches := test.expectedFn(theSource, collection, theStore) assertMatches(t, expectedMatches.Sorted(), actualResults.Sorted()) }) diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index b76686e4a9d..c86ae5c6264 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/scylladb/go-set/strset" + "github.com/stretchr/testify/require" "github.com/anchore/grype/grype/match" "github.com/anchore/syft/syft" @@ -70,16 +71,18 @@ func saveImage(t testing.TB, imageName string, destPath string) { } func getSyftSBOM(t testing.TB, image string, format sbom.Format) string { - sourceInput, err := source.ParseInput(image, "") + detection, err := source.Detect(image, source.DetectConfig{}) if err != nil { t.Fatalf("could not generate source input for packages command: %+v", err) } - src, cleanup, err := source.New(*sourceInput, nil, nil) + src, err := detection.NewSource(source.DetectionSourceConfig{}) if err != nil { t.Fatalf("can't get the source: %+v", err) } - t.Cleanup(cleanup) + t.Cleanup(func() { + require.NoError(t, src.Close()) + }) config := cataloger.DefaultConfig() config.Search.Scope = source.SquashedScope @@ -91,7 +94,7 @@ func getSyftSBOM(t testing.TB, image string, format sbom.Format) string { Packages: collection, LinuxDistribution: distro, }, - Source: src.Metadata, + Source: src.Describe(), } bytes, err := syft.Encode(s, format) From 742d84df458b0928861f6e264574e8e06eab824a Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 5 Jul 2023 15:28:54 -0400 Subject: [PATCH 2/2] fix linting Signed-off-by: Alex Goodman --- grype/pkg/syft_provider.go | 59 +++++++++++++----------- grype/presenter/internal/test_helpers.go | 1 + 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/grype/pkg/syft_provider.go b/grype/pkg/syft_provider.go index caf9ca74d14..8094f9794b5 100644 --- a/grype/pkg/syft_provider.go +++ b/grype/pkg/syft_provider.go @@ -8,36 +8,11 @@ import ( ) func syftProvider(userInput string, config ProviderConfig) ([]Package, Context, *sbom.SBOM, error) { - if config.CatalogingOptions.Search.Scope == "" { - return nil, Context{}, nil, errDoesNotProvide - } - - detection, err := source.Detect(userInput, source.DetectConfig{ - DefaultImageSource: config.DefaultImagePullSource, - }) + src, err := getSource(userInput, config) if err != nil { return nil, Context{}, nil, err } - var platform *image.Platform - if config.Platform != "" { - platform, err = image.NewPlatform(config.Platform) - if err != nil { - return nil, Context{}, nil, err - } - } - - src, err := detection.NewSource(source.DetectionSourceConfig{ - Alias: source.Alias{ - Name: config.Name, - }, - RegistryOptions: config.RegistryOptions, - Platform: platform, - Exclude: source.ExcludeConfig{ - Paths: config.Exclusions, - }, - }) - defer src.Close() catalog, relationships, theDistro, err := syft.CatalogPackages(src, config.CatalogingOptions) @@ -65,3 +40,35 @@ func syftProvider(userInput string, config ProviderConfig) ([]Package, Context, return packages, context, sbom, nil } + +func getSource(userInput string, config ProviderConfig) (source.Source, error) { + if config.CatalogingOptions.Search.Scope == "" { + return nil, errDoesNotProvide + } + + detection, err := source.Detect(userInput, source.DetectConfig{ + DefaultImageSource: config.DefaultImagePullSource, + }) + if err != nil { + return nil, err + } + + var platform *image.Platform + if config.Platform != "" { + platform, err = image.NewPlatform(config.Platform) + if err != nil { + return nil, err + } + } + + return detection.NewSource(source.DetectionSourceConfig{ + Alias: source.Alias{ + Name: config.Name, + }, + RegistryOptions: config.RegistryOptions, + Platform: platform, + Exclude: source.ExcludeConfig{ + Paths: config.Exclusions, + }, + }) +} diff --git a/grype/presenter/internal/test_helpers.go b/grype/presenter/internal/test_helpers.go index c5723697b31..c1471822b77 100644 --- a/grype/presenter/internal/test_helpers.go +++ b/grype/presenter/internal/test_helpers.go @@ -269,6 +269,7 @@ func generatePackages(t *testing.T) []pkg.Package { return updatedPkgs } +//nolint:funlen func generateContext(t *testing.T, scheme SyftSource) pkg.Context { var ( src syftSource.Source