From 7e97d762ea598f4a941a0e6e78d532f5f842c350 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 25 Oct 2022 11:17:11 -0400 Subject: [PATCH] port golang cataloger to new generic cataloger pattern Signed-off-by: Alex Goodman --- syft/pkg/cataloger/golang/binary_cataloger.go | 61 --------- syft/pkg/cataloger/golang/cataloger.go | 21 +++ syft/pkg/cataloger/golang/mod_cataloger.go | 17 --- syft/pkg/cataloger/golang/package.go | 69 ++++++++++ syft/pkg/cataloger/golang/package_test.go | 41 ++++++ .../{parse_go_bin.go => parse_go_binary.go} | 55 ++++---- ...go_bin_test.go => parse_go_binary_test.go} | 81 +++++++++--- syft/pkg/cataloger/golang/parse_go_mod.go | 37 +++--- .../pkg/cataloger/golang/parse_go_mod_test.go | 124 +++++++----------- .../golang/{scan_bin.go => scan_binary.go} | 0 syft/pkg/url.go | 8 -- syft/pkg/url_test.go | 20 +-- 12 files changed, 295 insertions(+), 239 deletions(-) delete mode 100644 syft/pkg/cataloger/golang/binary_cataloger.go create mode 100644 syft/pkg/cataloger/golang/cataloger.go delete mode 100644 syft/pkg/cataloger/golang/mod_cataloger.go create mode 100644 syft/pkg/cataloger/golang/package.go create mode 100644 syft/pkg/cataloger/golang/package_test.go rename syft/pkg/cataloger/golang/{parse_go_bin.go => parse_go_binary.go} (83%) rename syft/pkg/cataloger/golang/{parse_go_bin_test.go => parse_go_binary_test.go} (81%) rename syft/pkg/cataloger/golang/{scan_bin.go => scan_binary.go} (100%) diff --git a/syft/pkg/cataloger/golang/binary_cataloger.go b/syft/pkg/cataloger/golang/binary_cataloger.go deleted file mode 100644 index 494c7da86c23..000000000000 --- a/syft/pkg/cataloger/golang/binary_cataloger.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Package golang provides a concrete Cataloger implementation for go.mod files. -*/ -package golang - -import ( - "fmt" - - "github.com/anchore/syft/internal" - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" -) - -const catalogerName = "go-module-binary-cataloger" - -type Cataloger struct{} - -// NewGoModuleBinaryCataloger returns a new Golang cataloger object. -func NewGoModuleBinaryCataloger() *Cataloger { - return &Cataloger{} -} - -// Name returns a string that uniquely describes a cataloger -func (c *Cataloger) Name() string { - return catalogerName -} - -// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation. -func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { - var pkgs []pkg.Package - - fileMatches, err := resolver.FilesByMIMEType(internal.ExecutableMIMETypeSet.List()...) - if err != nil { - return pkgs, nil, fmt.Errorf("failed to find bin by mime types: %w", err) - } - - for _, location := range fileMatches { - readerCloser, err := resolver.FileContentsByLocation(location) - if err != nil { - log.Warnf("golang cataloger: opening file: %v", err) - continue - } - - reader, err := unionreader.GetUnionReader(readerCloser) - if err != nil { - return nil, nil, err - } - - mods, archs := scanFile(reader, location.RealPath) - internal.CloseAndLogError(readerCloser, location.RealPath) - - for i, mod := range mods { - pkgs = append(pkgs, buildGoPkgInfo(location, mod, archs[i])...) - } - } - - return pkgs, nil, nil -} diff --git a/syft/pkg/cataloger/golang/cataloger.go b/syft/pkg/cataloger/golang/cataloger.go new file mode 100644 index 000000000000..f49b25e656af --- /dev/null +++ b/syft/pkg/cataloger/golang/cataloger.go @@ -0,0 +1,21 @@ +/* +Package golang provides a concrete Cataloger implementation for go.mod files. +*/ +package golang + +import ( + "github.com/anchore/syft/internal" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// NewGoModFileCataloger returns a new Go module cataloger object. +func NewGoModFileCataloger() *generic.Cataloger { + return generic.NewCataloger("go-mod-file-cataloger"). + WithParserByGlobs(parseGoModFile, "**/go.mod") +} + +// NewGoModuleBinaryCataloger returns a new Golang cataloger object. +func NewGoModuleBinaryCataloger() *generic.Cataloger { + return generic.NewCataloger("go-module-binary-cataloger"). + WithParserByMimeTypes(parseGoBinary, internal.ExecutableMIMETypeSet.List()...) +} diff --git a/syft/pkg/cataloger/golang/mod_cataloger.go b/syft/pkg/cataloger/golang/mod_cataloger.go deleted file mode 100644 index 50ce25923715..000000000000 --- a/syft/pkg/cataloger/golang/mod_cataloger.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -Package golang provides a concrete Cataloger implementation for go.mod files. -*/ -package golang - -import ( - "github.com/anchore/syft/syft/pkg/cataloger/common" -) - -// NewGoModFileCataloger returns a new Go module cataloger object. -func NewGoModFileCataloger() *common.GenericCataloger { - globParsers := map[string]common.ParserFn{ - "**/go.mod": parseGoMod, - } - - return common.NewGenericCataloger(nil, globParsers, "go-mod-file-cataloger") -} diff --git a/syft/pkg/cataloger/golang/package.go b/syft/pkg/cataloger/golang/package.go new file mode 100644 index 000000000000..93f762a5d9af --- /dev/null +++ b/syft/pkg/cataloger/golang/package.go @@ -0,0 +1,69 @@ +package golang + +import ( + "regexp" + "runtime/debug" + "strings" + + "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/source" +) + +func newGoBinaryPackage(dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...source.Location) pkg.Package { + if dep.Replace != nil { + dep = dep.Replace + } + + p := pkg.Package{ + Name: dep.Path, + Version: dep.Version, + PURL: packageURL(dep.Path, dep.Version), + Language: pkg.Go, + Type: pkg.GoModulePkg, + Locations: source.NewLocationSet(locations...), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goVersion, + H1Digest: dep.Sum, + Architecture: architecture, + BuildSettings: buildSettings, + MainModule: mainModule, + }, + } + + p.SetID() + + return p +} + +func packageURL(moduleName, moduleVersion string) string { + // source: https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang + // note: "The version is often empty when a commit is not specified and should be the commit in most cases when available." + + re := regexp.MustCompile(`(/)[^/]*$`) + fields := re.Split(moduleName, -1) + if len(fields) == 0 { + return "" + } + namespace := fields[0] + name := strings.TrimPrefix(strings.TrimPrefix(moduleName, namespace), "/") + + if name == "" { + // this is a "short" url (with no namespace) + name = namespace + namespace = "" + } + + // The subpath is used to point to a subpath inside a package (e.g. pkg:golang/google.golang.org/genproto#googleapis/api/annotations) + subpath := "" // TODO: not implemented + + return packageurl.NewPackageURL( + packageurl.TypeGolang, + namespace, + name, + moduleVersion, + nil, + subpath, + ).ToString() +} diff --git a/syft/pkg/cataloger/golang/package_test.go b/syft/pkg/cataloger/golang/package_test.go new file mode 100644 index 000000000000..d08d239a9183 --- /dev/null +++ b/syft/pkg/cataloger/golang/package_test.go @@ -0,0 +1,41 @@ +package golang + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/pkg" +) + +func Test_packageURL(t *testing.T) { + + tests := []struct { + name string + pkg pkg.Package + expected string + }{ + { + name: "gocase", + pkg: pkg.Package{ + Name: "github.com/anchore/syft", + Version: "v0.1.0", + }, + expected: "pkg:golang/github.com/anchore/syft@v0.1.0", + }, + { + name: "golang short name", + pkg: pkg.Package{ + Name: "go.opencensus.io", + Version: "v0.23.0", + }, + expected: "pkg:golang/go.opencensus.io@v0.23.0", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.expected, packageURL(test.pkg.Name, test.pkg.Version)) + }) + } +} diff --git a/syft/pkg/cataloger/golang/parse_go_bin.go b/syft/pkg/cataloger/golang/parse_go_binary.go similarity index 83% rename from syft/pkg/cataloger/golang/parse_go_bin.go rename to syft/pkg/cataloger/golang/parse_go_binary.go index cabf30f32241..31f6ca2cd7be 100644 --- a/syft/pkg/cataloger/golang/parse_go_bin.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -14,9 +14,13 @@ import ( "golang.org/x/mod/module" + "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/golang/internal/xcoff" + "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" "github.com/anchore/syft/syft/source" ) @@ -34,9 +38,27 @@ var ( const devel = "(devel)" +// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation. +func parseGoBinary(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + var pkgs []pkg.Package + + unionReader, err := unionreader.GetUnionReader(reader.ReadCloser) + if err != nil { + return nil, nil, err + } + + mods, archs := scanFile(unionReader, reader.RealPath) + internal.CloseAndLogError(reader.ReadCloser, reader.RealPath) + + for i, mod := range mods { + pkgs = append(pkgs, buildGoPkgInfo(reader.Location, mod, archs[i])...) + } + return pkgs, nil, nil +} + func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Location) pkg.Package { gbs := getBuildSettings(mod.Settings) - main := newGoBinaryPackage(&mod.Main, mod.Main.Path, mod.GoVersion, arch, location, gbs) + main := newGoBinaryPackage(&mod.Main, mod.Main.Path, mod.GoVersion, arch, gbs, location) if main.Version == devel { if version, ok := gbs["vcs.revision"]; ok { if timestamp, ok := gbs["vcs.time"]; ok { @@ -50,39 +72,14 @@ func makeGoMainPackage(mod *debug.BuildInfo, arch string, location source.Locati version = module.PseudoVersion("", "", ts, version) } main.Version = version + main.PURL = packageURL(main.Name, main.Version) + main.SetID() } } return main } -func newGoBinaryPackage(dep *debug.Module, mainModule, goVersion, architecture string, location source.Location, buildSettings map[string]string) pkg.Package { - if dep.Replace != nil { - dep = dep.Replace - } - - p := pkg.Package{ - FoundBy: catalogerName, - Name: dep.Path, - Version: dep.Version, - Language: pkg.Go, - Type: pkg.GoModulePkg, - Locations: source.NewLocationSet(location), - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{ - GoCompiledVersion: goVersion, - H1Digest: dep.Sum, - Architecture: architecture, - BuildSettings: buildSettings, - MainModule: mainModule, - }, - } - - p.SetID() - - return p -} - // getArchs finds a binary architecture by two ways: // 1) reading build info from binaries compiled by go1.18+ // 2) reading file headers from binaries compiled by < go1.18 @@ -192,7 +189,7 @@ func buildGoPkgInfo(location source.Location, mod *debug.BuildInfo, arch string) if dep == nil { continue } - p := newGoBinaryPackage(dep, mod.Main.Path, mod.GoVersion, arch, location, nil) + p := newGoBinaryPackage(dep, mod.Main.Path, mod.GoVersion, arch, nil, location) if pkg.IsValid(&p) { pkgs = append(pkgs, p) } diff --git a/syft/pkg/cataloger/golang/parse_go_bin_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go similarity index 81% rename from syft/pkg/cataloger/golang/parse_go_bin_test.go rename to syft/pkg/cataloger/golang/parse_go_binary_test.go index 6f48c02b9ad3..acfd435c59a0 100644 --- a/syft/pkg/cataloger/golang/parse_go_bin_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -123,18 +123,18 @@ func TestBuildGoPkgInfo(t *testing.T) { goCompiledVersion = "1.18" archDetails = "amd64" ) - buildSettings := map[string]string{ + defaultBuildSettings := map[string]string{ "GOARCH": "amd64", "GOOS": "darwin", "GOAMD64": "v1", } - expectedMain := pkg.Package{ + unmodifiedMain := pkg.Package{ Name: "github.com/anchore/syft", - FoundBy: catalogerName, Language: pkg.Go, Type: pkg.GoModulePkg, Version: "(devel)", + PURL: "pkg:golang/github.com/anchore/syft@(devel)", Locations: source.NewLocationSet( source.Location{ Coordinates: source.Coordinates{ @@ -147,7 +147,7 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinMetadata{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: buildSettings, + BuildSettings: defaultBuildSettings, MainModule: "github.com/anchore/syft", }, } @@ -159,7 +159,7 @@ func TestBuildGoPkgInfo(t *testing.T) { expected []pkg.Package }{ { - name: "buildGoPkgInfo parses a nil mod", + name: "parse an empty mod", mod: nil, expected: []pkg.Package(nil), }, @@ -179,7 +179,7 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package{ { Name: "github.com/adrg/xdg", - FoundBy: catalogerName, + PURL: "pkg:golang/github.com/adrg/xdg", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -201,7 +201,7 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package(nil), }, { - name: "buildGoPkgInfo parses a mod without main module", + name: "parse a mod without main module", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -221,8 +221,8 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package{ { Name: "github.com/adrg/xdg", - FoundBy: catalogerName, Version: "v0.2.1", + PURL: "pkg:golang/github.com/adrg/xdg@v0.2.1", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -243,7 +243,7 @@ func TestBuildGoPkgInfo(t *testing.T) { }, }, { - name: "buildGoPkgInfo parses a mod without packages", + name: "parse a mod without packages", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -254,10 +254,55 @@ func TestBuildGoPkgInfo(t *testing.T) { {Key: "GOAMD64", Value: "v1"}, }, }, - expected: []pkg.Package{expectedMain}, + expected: []pkg.Package{unmodifiedMain}, }, { - name: "buildGoPkgInfo parses a populated mod string and returns packages but no source info", + name: "parse main mod and replace devel version", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, + {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.0.0-20221014195457-41bc6bb41035", + PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035", + Locations: source.NewLocationSet( + source.Location{ + Coordinates: source.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + }, + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", + "vcs.time": "2022-10-14T19:54:57Z", + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, + { + name: "parse a populated mod string and returns packages but no source info", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -283,8 +328,8 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package{ { Name: "github.com/adrg/xdg", - FoundBy: catalogerName, Version: "v0.2.1", + PURL: "pkg:golang/github.com/adrg/xdg@v0.2.1", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -305,8 +350,8 @@ func TestBuildGoPkgInfo(t *testing.T) { }, { Name: "github.com/anchore/client-go", - FoundBy: catalogerName, Version: "v0.0.0-20210222170800-9c70f9b80bcf", + PURL: "pkg:golang/github.com/anchore/client-go@v0.0.0-20210222170800-9c70f9b80bcf", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -325,11 +370,11 @@ func TestBuildGoPkgInfo(t *testing.T) { MainModule: "github.com/anchore/syft", }, }, - expectedMain, + unmodifiedMain, }, }, { - name: "buildGoPkgInfo parses a populated mod string and returns packages when a replace directive exists", + name: "parse a populated mod string and returns packages when a replace directive exists", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -360,8 +405,8 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package{ { Name: "golang.org/x/sys", - FoundBy: catalogerName, Version: "v0.0.0-20211006194710-c8a6f5223071", + PURL: "pkg:golang/golang.org/x/sys@v0.0.0-20211006194710-c8a6f5223071", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -381,8 +426,8 @@ func TestBuildGoPkgInfo(t *testing.T) { }}, { Name: "golang.org/x/term", - FoundBy: catalogerName, Version: "v0.0.0-20210916214954-140adaaadfaf", + PURL: "pkg:golang/golang.org/x/term@v0.0.0-20210916214954-140adaaadfaf", Language: pkg.Go, Type: pkg.GoModulePkg, Locations: source.NewLocationSet( @@ -401,7 +446,7 @@ func TestBuildGoPkgInfo(t *testing.T) { MainModule: "github.com/anchore/syft", }, }, - expectedMain, + unmodifiedMain, }, }, } diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index db03f4a9914c..7f120514fcbf 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -9,38 +9,44 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" + "github.com/anchore/syft/syft/source" ) -// parseGoMod takes a go.mod and lists all packages discovered. -func parseGoMod(path string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { - packages := make(map[string]*pkg.Package) +// parseGoModFile takes a go.mod and lists all packages discovered. +func parseGoModFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + packages := make(map[string]pkg.Package) contents, err := io.ReadAll(reader) if err != nil { return nil, nil, fmt.Errorf("failed to read go module: %w", err) } - file, err := modfile.Parse(path, contents, nil) + file, err := modfile.Parse(reader.RealPath, contents, nil) if err != nil { return nil, nil, fmt.Errorf("failed to parse go module: %w", err) } for _, m := range file.Require { - packages[m.Mod.Path] = &pkg.Package{ - Name: m.Mod.Path, - Version: m.Mod.Version, - Language: pkg.Go, - Type: pkg.GoModulePkg, + packages[m.Mod.Path] = pkg.Package{ + Name: m.Mod.Path, + Version: m.Mod.Version, + Locations: source.NewLocationSet(reader.Location), + PURL: packageURL(m.Mod.Path, m.Mod.Version), + Language: pkg.Go, + Type: pkg.GoModulePkg, } } // remove any old packages and replace with new ones... for _, m := range file.Replace { - packages[m.New.Path] = &pkg.Package{ - Name: m.New.Path, - Version: m.New.Version, - Language: pkg.Go, - Type: pkg.GoModulePkg, + packages[m.New.Path] = pkg.Package{ + Name: m.New.Path, + Version: m.New.Version, + Locations: source.NewLocationSet(reader.Location), + PURL: packageURL(m.New.Path, m.New.Version), + Language: pkg.Go, + Type: pkg.GoModulePkg, } } @@ -49,9 +55,10 @@ func parseGoMod(path string, reader io.Reader) ([]*pkg.Package, []artifact.Relat delete(packages, m.Mod.Path) } - pkgsSlice := make([]*pkg.Package, len(packages)) + pkgsSlice := make([]pkg.Package, len(packages)) idx := 0 for _, p := range packages { + p.SetID() pkgsSlice[idx] = p idx++ } diff --git a/syft/pkg/cataloger/golang/parse_go_mod_test.go b/syft/pkg/cataloger/golang/parse_go_mod_test.go index f685024b9921..4cd663c6ba4f 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod_test.go +++ b/syft/pkg/cataloger/golang/parse_go_mod_test.go @@ -1,63 +1,74 @@ package golang import ( - "os" "testing" - "github.com/go-test/deep" - "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" + "github.com/anchore/syft/syft/source" ) func TestParseGoMod(t *testing.T) { tests := []struct { fixture string - expected map[string]pkg.Package + expected []pkg.Package }{ { fixture: "test-fixtures/one-package", - expected: map[string]pkg.Package{ - "github.com/bmatcuk/doublestar": { - Name: "github.com/bmatcuk/doublestar", - Version: "v1.3.1", - Language: pkg.Go, - Type: pkg.GoModulePkg, + expected: []pkg.Package{ + { + Name: "github.com/bmatcuk/doublestar", + Version: "v1.3.1", + PURL: "pkg:golang/github.com/bmatcuk/doublestar@v1.3.1", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/one-package")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, }, }, { fixture: "test-fixtures/many-packages", - expected: map[string]pkg.Package{ - "github.com/anchore/go-testutils": { - Name: "github.com/anchore/go-testutils", - Version: "v0.0.0-20200624184116-66aa578126db", - Language: pkg.Go, - Type: pkg.GoModulePkg, + expected: []pkg.Package{ + { + Name: "github.com/anchore/go-testutils", + Version: "v0.0.0-20200624184116-66aa578126db", + PURL: "pkg:golang/github.com/anchore/go-testutils@v0.0.0-20200624184116-66aa578126db", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, - "github.com/anchore/go-version": { - Name: "github.com/anchore/go-version", - Version: "v1.2.2-0.20200701162849-18adb9c92b9b", - Language: pkg.Go, - Type: pkg.GoModulePkg, + { + Name: "github.com/anchore/go-version", + Version: "v1.2.2-0.20200701162849-18adb9c92b9b", + PURL: "pkg:golang/github.com/anchore/go-version@v1.2.2-0.20200701162849-18adb9c92b9b", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, - "github.com/anchore/stereoscope": { - Name: "github.com/anchore/stereoscope", - Version: "v0.0.0-20200706164556-7cf39d7f4639", - Language: pkg.Go, - Type: pkg.GoModulePkg, + { + Name: "github.com/anchore/stereoscope", + Version: "v0.0.0-20200706164556-7cf39d7f4639", + PURL: "pkg:golang/github.com/anchore/stereoscope@v0.0.0-20200706164556-7cf39d7f4639", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, - "github.com/bmatcuk/doublestar": { - Name: "github.com/bmatcuk/doublestar", - Version: "v8.8.8", - Language: pkg.Go, - Type: pkg.GoModulePkg, + { + Name: "github.com/bmatcuk/doublestar", + Version: "v8.8.8", + PURL: "pkg:golang/github.com/bmatcuk/doublestar@v8.8.8", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, - "github.com/go-test/deep": { - Name: "github.com/go-test/deep", - Version: "v1.0.6", - Language: pkg.Go, - Type: pkg.GoModulePkg, + { + Name: "github.com/go-test/deep", + Version: "v1.0.6", + PURL: "pkg:golang/github.com/go-test/deep@v1.0.6", + Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Language: pkg.Go, + Type: pkg.GoModulePkg, }, }, }, @@ -65,43 +76,10 @@ func TestParseGoMod(t *testing.T) { for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { - f, err := os.Open(test.fixture) - if err != nil { - t.Fatalf(err.Error()) - } - - // TODO: no relationships are under test yet - actual, _, err := parseGoMod(test.fixture, f) - if err != nil { - t.Fatalf(err.Error()) - } - - if len(actual) != len(test.expected) { - t.Fatalf("unexpected length: %d", len(actual)) - } - - for _, a := range actual { - e, ok := test.expected[a.Name] - if !ok { - t.Errorf("extra package: %s", a.Name) - continue - } - - diffs := deep.Equal(a, &e) - if len(diffs) > 0 { - t.Errorf("diffs found for %q", a.Name) - for _, d := range diffs { - t.Errorf("diff: %+v", d) - } - } - } - - if t.Failed() { - for _, a := range actual { - t.Logf("Found: %+v", a) - } - } - + pkgtest.NewCatalogTester(). + FromFile(t, test.fixture). + Expects(test.expected, nil). + TestParser(t, parseGoModFile) }) } } diff --git a/syft/pkg/cataloger/golang/scan_bin.go b/syft/pkg/cataloger/golang/scan_binary.go similarity index 100% rename from syft/pkg/cataloger/golang/scan_bin.go rename to syft/pkg/cataloger/golang/scan_binary.go diff --git a/syft/pkg/url.go b/syft/pkg/url.go index da4b62c30421..bfb689b2e028 100644 --- a/syft/pkg/url.go +++ b/syft/pkg/url.go @@ -1,7 +1,6 @@ package pkg import ( - "regexp" "sort" "strings" @@ -42,13 +41,6 @@ func URL(p Package, release *linux.Release) string { switch { case purlType == "": purlType = packageurl.TypeGeneric - case p.Type == GoModulePkg: - re := regexp.MustCompile(`(/)[^/]*$`) - fields := re.Split(p.Name, -1) - if len(fields) > 1 { - namespace = fields[0] - name = strings.TrimPrefix(p.Name, namespace+"/") - } case p.Type == NpmPkg: fields := strings.SplitN(p.Name, "/", 2) if len(fields) > 1 { diff --git a/syft/pkg/url_test.go b/syft/pkg/url_test.go index 9afa9c350ee9..0d47b8edc3cc 100644 --- a/syft/pkg/url_test.go +++ b/syft/pkg/url_test.go @@ -17,24 +17,6 @@ func TestPackageURL(t *testing.T) { distro *linux.Release expected string }{ - { - name: "golang", - pkg: Package{ - Name: "github.com/anchore/syft", - Version: "v0.1.0", - Type: GoModulePkg, - }, - expected: "pkg:golang/github.com/anchore/syft@v0.1.0", - }, - { - name: "golang short name", - pkg: Package{ - Name: "go.opencensus.io", - Version: "v0.23.0", - Type: GoModulePkg, - }, - expected: "pkg:golang/go.opencensus.io@v0.23.0", - }, { name: "python", pkg: Package{ @@ -199,6 +181,8 @@ func TestPackageURL(t *testing.T) { expectedTypes.Remove(string(ConanPkg)) expectedTypes.Remove(string(DartPubPkg)) expectedTypes.Remove(string(DotnetPkg)) + expectedTypes.Remove(string(DebPkg)) + expectedTypes.Remove(string(GoModulePkg)) for _, test := range tests { t.Run(test.name, func(t *testing.T) {