Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
Christopher Phillips 2024-09-18 13:14:12 -04:00
parent 7934696463
commit 029b948d73
No known key found for this signature in database
6 changed files with 169 additions and 1 deletions

2
go.mod
View file

@ -232,7 +232,7 @@ require (
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/tools v0.25.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
google.golang.org/grpc v1.62.1 // indirect

2
go.sum
View file

@ -1153,6 +1153,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -84,6 +84,12 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
},
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Go, Golang, "gomod",
),
newPackageTaskFactory(
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
return golang.NewGoLibraryCataloger(cfg.PackagesConfig.Golang)
},
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Go, Golang, "gomod",
),
newSimplePackageTaskFactory(java.NewGradleLockfileCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, Java, "gradle"),
newPackageTaskFactory(
func(cfg CatalogingFactoryConfig) pkg.Cataloger {

View file

@ -16,6 +16,7 @@ var versionCandidateGroups = regexp.MustCompile(`(?P<version>\d+(\.\d+)?(\.\d+)?
const (
modFileCatalogerName = "go-module-file-cataloger"
binaryCatalogerName = "go-module-binary-cataloger"
libraryCatalogerName = "go-module-library-cataloger"
)
// NewGoModuleFileCataloger returns a new cataloger object that searches within go.mod files.
@ -33,3 +34,8 @@ func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
).
WithProcessors(stdlibProcessor)
}
func NewGoLibraryCataloger(opts CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger(libraryCatalogerName).
WithParserByGlobs(newGoLibraryCataloger().parseGoModFile, "**/go.mod")
}

View file

@ -0,0 +1,153 @@
package golang
import (
"context"
"fmt"
"path/filepath"
"strings"
"golang.org/x/tools/go/packages"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"golang.org/x/mod/modfile"
"io"
)
type goLibraryCataloger struct{}
func newGoLibraryCataloger() *goLibraryCataloger {
return &goLibraryCataloger{}
}
func (c *goLibraryCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
contents, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to read go module: %w", err)
}
modPath := modfile.ModulePath(contents)
pkgInfo, err := libraries(ctx, modPath)
if err != nil {
return nil, nil, fmt.Errorf("failed to determine package info: %w", err)
}
pkgInfoNoOp(pkgInfo)
return nil, nil, nil
}
func pkgInfoNoOp(info []pkgInfo) []*pkg.Package {
return []*pkg.Package{}
}
// These are the steps we take to find libraries:
// 1. we list all modules and all packages
// 2. for each package, we find a list of candidates
// 3. we deduplicate all candidates
// 4. for each candidate, we classify if the candidate is a license file
// 5. for each package, we select the first candidates that is a license
// file & add the package to a list of packages for that license file
// 6. we return an array of libraries (which are the license files, the
// found licenses in that file, all the packages that had that file as
// its first candidate and the module in which those packages live)
func libraries(ctx context.Context, modPath string) ([]pkgInfo, error) {
cfg := &packages.Config{
Context: ctx,
Mode: packages.NeedImports | packages.NeedDeps | packages.NeedFiles | packages.NeedName | packages.NeedModule,
Tests: true, // TODO: should we inject this to be configurable
}
rootPkgs, err := packages.Load(cfg, modPath)
if err != nil {
return nil, err
}
vendoredSearch := []*Module{}
for _, parentPkg := range rootPkgs {
if parentPkg.Module == nil {
continue
}
module := newModule(parentPkg.Module)
if module.Dir == "" {
continue
}
vendoredSearch = append(vendoredSearch, module)
}
allPackages := []pkgInfo{}
{
pkgErrorOccurred := false
packages.Visit(rootPkgs, func(p *packages.Package) bool {
if len(p.Errors) > 0 {
pkgErrorOccurred = true
return false
}
if len(p.OtherFiles) > 0 {
// log.Warningf("%q contains non-Go code that can't be inspected for further dependencies:\n%s", p.PkgPath, strings.Join(p.OtherFiles, "\n"))
}
var pkgDir string
switch {
case len(p.GoFiles) > 0:
pkgDir = filepath.Dir(p.GoFiles[0])
case len(p.CompiledGoFiles) > 0:
pkgDir = filepath.Dir(p.CompiledGoFiles[0])
case len(p.OtherFiles) > 0:
pkgDir = filepath.Dir(p.OtherFiles[0])
default:
// This package is empty - nothing to do.
return true
}
module := newModule(p.Module)
allPackages = append(allPackages, pkgInfo{
pkgPath: p.PkgPath,
modulePath: module.Path,
pkgDir: pkgDir,
moduleDir: module.Dir,
})
return true
}, nil)
if pkgErrorOccurred {
return nil, fmt.Errorf("failed to parse go modules")
}
}
return allPackages, nil
}
func newModule(mod *packages.Module) *Module {
tmp := *mod
if tmp.Replace != nil {
tmp = *tmp.Replace
}
// The +incompatible suffix does not affect module version.
// ref: https://golang.org/ref/mod#incompatible-versions
tmp.Version = strings.TrimSuffix(tmp.Version, "+incompatible")
return &Module{
Path: tmp.Path,
Version: tmp.Version,
Dir: tmp.Dir,
}
}
type Module struct {
Path string
Version string
Dir string
}
type pkgInfo struct {
// pkgPath is the import path of the package.
pkgPath string
// modulePath is the module path of the package.
modulePath string
// pkgDir is the directory containing the package's source code.
pkgDir string
// moduleDir is the directory containing the module's source code.
moduleDir string
}

View file

@ -0,0 +1 @@
package golang