mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 08:53:15 +01:00
add relationships for go binary packages (#2912)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
ac34808b9c
commit
f4a69e6d35
@ -4,16 +4,9 @@ Package golang provides a concrete Cataloger implementation relating to packages
|
|||||||
package golang
|
package golang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
|
||||||
"github.com/anchore/syft/internal/mimetype"
|
"github.com/anchore/syft/internal/mimetype"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
|
||||||
"github.com/anchore/syft/syft/cpe"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
)
|
)
|
||||||
@ -30,102 +23,17 @@ func NewGoModuleFileCataloger(opts CatalogerConfig) pkg.Cataloger {
|
|||||||
c := goModCataloger{
|
c := goModCataloger{
|
||||||
licenses: newGoLicenses(modFileCatalogerName, opts),
|
licenses: newGoLicenses(modFileCatalogerName, opts),
|
||||||
}
|
}
|
||||||
return &progressingCataloger{
|
|
||||||
cataloger: generic.NewCataloger(modFileCatalogerName).
|
return generic.NewCataloger(modFileCatalogerName).
|
||||||
WithParserByGlobs(c.parseGoModFile, "**/go.mod"),
|
WithParserByGlobs(c.parseGoModFile, "**/go.mod")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler.
|
// NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler.
|
||||||
func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
|
func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
|
||||||
return &progressingCataloger{
|
return generic.NewCataloger(binaryCatalogerName).
|
||||||
cataloger: generic.NewCataloger(binaryCatalogerName).
|
|
||||||
WithParserByMimeTypes(
|
WithParserByMimeTypes(
|
||||||
newGoBinaryCataloger(opts).parseGoBinary,
|
newGoBinaryCataloger(opts).parseGoBinary,
|
||||||
mimetype.ExecutableMIMETypeSet.List()...,
|
mimetype.ExecutableMIMETypeSet.List()...,
|
||||||
),
|
).
|
||||||
}
|
WithProcessors(stdlibProcessor)
|
||||||
}
|
|
||||||
|
|
||||||
type progressingCataloger struct {
|
|
||||||
cataloger *generic.Cataloger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *progressingCataloger) Name() string {
|
|
||||||
return p.cataloger.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *progressingCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
|
||||||
pkgs, relationships, err := p.cataloger.Catalog(ctx, resolver)
|
|
||||||
goCompilerPkgs := []pkg.Package{}
|
|
||||||
totalLocations := file.NewLocationSet()
|
|
||||||
for _, goPkg := range pkgs {
|
|
||||||
mValue, ok := goPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// go binary packages should only contain a single location
|
|
||||||
for _, location := range goPkg.Locations.ToSlice() {
|
|
||||||
if !totalLocations.Contains(location) {
|
|
||||||
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
|
|
||||||
if stdLibPkg != nil {
|
|
||||||
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
|
|
||||||
totalLocations.Add(location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, goCompilerPkgs...)
|
|
||||||
return pkgs, relationships, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
|
|
||||||
stdlibCpe, err := generateStdlibCpe(version)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
goCompilerPkg := &pkg.Package{
|
|
||||||
Name: "stdlib",
|
|
||||||
Version: version,
|
|
||||||
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
|
|
||||||
CPEs: []cpe.CPE{stdlibCpe},
|
|
||||||
Locations: location,
|
|
||||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
|
|
||||||
Language: pkg.Go,
|
|
||||||
Type: pkg.GoModulePkg,
|
|
||||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
|
||||||
GoCompiledVersion: version,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
goCompilerPkg.SetID()
|
|
||||||
|
|
||||||
return goCompilerPkg
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
|
|
||||||
// GoCompiledVersion when pulled from a binary is prefixed by go
|
|
||||||
version = strings.TrimPrefix(version, "go")
|
|
||||||
|
|
||||||
// we also need to trim starting from the first +<metadata> to
|
|
||||||
// correctly extract potential rc candidate information for cpe generation
|
|
||||||
// ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned
|
|
||||||
after, _, found := strings.Cut("+", version)
|
|
||||||
if found {
|
|
||||||
version = after
|
|
||||||
}
|
|
||||||
|
|
||||||
// extracting <version> and <candidate>
|
|
||||||
// https://regex101.com/r/985GsI/1
|
|
||||||
captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version)
|
|
||||||
vr, ok := captureGroups["version"]
|
|
||||||
if !ok || vr == "" {
|
|
||||||
return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"])
|
|
||||||
if candidate, ok := captureGroups["candidate"]; ok && candidate != "" {
|
|
||||||
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cpe.New(cpeString, cpe.GeneratedSource)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,92 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Test_PackageCataloger_Binary(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
expectedPkgs []string
|
||||||
|
expectedRels []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple module with dependencies",
|
||||||
|
fixture: "image-small",
|
||||||
|
expectedPkgs: []string{
|
||||||
|
"anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
|
||||||
|
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
|
||||||
|
"github.com/golang/snappy @ v0.0.2 (/run-me)",
|
||||||
|
"github.com/klauspost/compress @ v1.11.4 (/run-me)",
|
||||||
|
"github.com/klauspost/pgzip @ v1.2.5 (/run-me)",
|
||||||
|
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me)",
|
||||||
|
"github.com/nwaples/rardecode @ v1.1.0 (/run-me)",
|
||||||
|
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me)",
|
||||||
|
"github.com/ulikunitz/xz @ v0.5.9 (/run-me)",
|
||||||
|
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me)",
|
||||||
|
"stdlib @ go1.22.3 (/run-me)",
|
||||||
|
},
|
||||||
|
expectedRels: []string{
|
||||||
|
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
"stdlib @ go1.22.3 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partially built binary",
|
||||||
|
// the difference is the build flags used to build the binary... they will not reference the module directly
|
||||||
|
// see the dockerfile for details
|
||||||
|
fixture: "image-not-a-module",
|
||||||
|
expectedPkgs: []string{
|
||||||
|
"command-line-arguments @ (devel) (/run-me)", // this is the difference!
|
||||||
|
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
|
||||||
|
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
|
||||||
|
"github.com/golang/snappy @ v0.0.2 (/run-me)",
|
||||||
|
"github.com/klauspost/compress @ v1.11.4 (/run-me)",
|
||||||
|
"github.com/klauspost/pgzip @ v1.2.5 (/run-me)",
|
||||||
|
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me)",
|
||||||
|
"github.com/nwaples/rardecode @ v1.1.0 (/run-me)",
|
||||||
|
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me)",
|
||||||
|
"github.com/ulikunitz/xz @ v0.5.9 (/run-me)",
|
||||||
|
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me)",
|
||||||
|
"stdlib @ go1.22.3 (/run-me)",
|
||||||
|
},
|
||||||
|
expectedRels: []string{
|
||||||
|
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
"stdlib @ go1.22.3 (/run-me) [dependency-of] command-line-arguments @ (devel) (/run-me)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
WithImageResolver(t, test.fixture).
|
||||||
|
ExpectsPackageStrings(test.expectedPkgs).
|
||||||
|
ExpectsRelationshipStrings(test.expectedRels).
|
||||||
|
TestCataloger(t, NewGoModuleBinaryCataloger(DefaultCatalogerConfig()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func Test_Mod_Cataloger_Globs(t *testing.T) {
|
func Test_Mod_Cataloger_Globs(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -69,17 +69,38 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
|
|||||||
mods := scanFile(unionReader, reader.RealPath)
|
mods := scanFile(unionReader, reader.RealPath)
|
||||||
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
||||||
|
|
||||||
|
var rels []artifact.Relationship
|
||||||
for _, mod := range mods {
|
for _, mod := range mods {
|
||||||
pkgs = append(pkgs, c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch, unionReader)...)
|
var depPkgs []pkg.Package
|
||||||
|
mainPkg, depPkgs := c.buildGoPkgInfo(resolver, reader.Location, mod, mod.arch, unionReader)
|
||||||
|
if mainPkg != nil {
|
||||||
|
rels = createModuleRelationships(*mainPkg, depPkgs)
|
||||||
|
pkgs = append(pkgs, *mainPkg)
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, depPkgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, rels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) []pkg.Package {
|
func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship {
|
||||||
|
var relationships []artifact.Relationship
|
||||||
|
|
||||||
|
for _, dep := range deps {
|
||||||
|
relationships = append(relationships, artifact.Relationship{
|
||||||
|
From: dep,
|
||||||
|
To: main,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return relationships
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) (*pkg.Package, []pkg.Package) {
|
||||||
var pkgs []pkg.Package
|
var pkgs []pkg.Package
|
||||||
if mod == nil {
|
if mod == nil {
|
||||||
return pkgs
|
return nil, pkgs
|
||||||
}
|
}
|
||||||
|
|
||||||
var empty debug.Module
|
var empty debug.Module
|
||||||
@ -110,13 +131,12 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mod.Main == empty {
|
if mod.Main == empty {
|
||||||
return pkgs
|
return nil, pkgs
|
||||||
}
|
}
|
||||||
|
|
||||||
main := c.makeGoMainPackage(resolver, mod, arch, location, reader)
|
main := c.makeGoMainPackage(resolver, mod, arch, location, reader)
|
||||||
pkgs = append(pkgs, main)
|
|
||||||
|
|
||||||
return pkgs
|
return &main, pkgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
|
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
|
||||||
|
|||||||
@ -964,7 +964,10 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
|||||||
c := newGoBinaryCataloger(DefaultCatalogerConfig())
|
c := newGoBinaryCataloger(DefaultCatalogerConfig())
|
||||||
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader(test.binaryContent)))
|
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader(test.binaryContent)))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
|
mainPkg, pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
|
||||||
|
if mainPkg != nil {
|
||||||
|
pkgs = append(pkgs, *mainPkg)
|
||||||
|
}
|
||||||
require.Len(t, pkgs, len(test.expected))
|
require.Len(t, pkgs, len(test.expected))
|
||||||
for i, p := range pkgs {
|
for i, p := range pkgs {
|
||||||
pkgtest.AssertPackagesEqual(t, test.expected[i], p)
|
pkgtest.AssertPackagesEqual(t, test.expected[i], p)
|
||||||
|
|||||||
100
syft/pkg/cataloger/golang/stdlib_package.go
Normal file
100
syft/pkg/cataloger/golang/stdlib_package.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stdlibProcessor(pkgs []pkg.Package, relationships []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
compilerPkgs, newRelationships := stdlibPackageAndRelationships(pkgs)
|
||||||
|
return append(pkgs, compilerPkgs...), append(relationships, newRelationships...), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func stdlibPackageAndRelationships(pkgs []pkg.Package) ([]pkg.Package, []artifact.Relationship) {
|
||||||
|
var goCompilerPkgs []pkg.Package
|
||||||
|
var relationships []artifact.Relationship
|
||||||
|
totalLocations := file.NewLocationSet()
|
||||||
|
for _, goPkg := range pkgs {
|
||||||
|
mValue, ok := goPkg.Metadata.(pkg.GolangBinaryBuildinfoEntry)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// go binary packages should only contain a single location
|
||||||
|
for _, location := range goPkg.Locations.ToSlice() {
|
||||||
|
if totalLocations.Contains(location) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
|
||||||
|
if stdLibPkg != nil {
|
||||||
|
goCompilerPkgs = append(goCompilerPkgs, *stdLibPkg)
|
||||||
|
totalLocations.Add(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
relationships = append(relationships, artifact.Relationship{
|
||||||
|
From: *stdLibPkg,
|
||||||
|
To: goPkg,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return goCompilerPkgs, relationships
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
|
||||||
|
stdlibCpe, err := generateStdlibCpe(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
goCompilerPkg := &pkg.Package{
|
||||||
|
Name: "stdlib",
|
||||||
|
Version: version,
|
||||||
|
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
|
||||||
|
CPEs: []cpe.CPE{stdlibCpe},
|
||||||
|
Locations: location,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
|
||||||
|
Language: pkg.Go,
|
||||||
|
Type: pkg.GoModulePkg,
|
||||||
|
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||||
|
GoCompiledVersion: version,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
goCompilerPkg.SetID()
|
||||||
|
|
||||||
|
return goCompilerPkg
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateStdlibCpe(version string) (stdlibCpe cpe.CPE, err error) {
|
||||||
|
// GoCompiledVersion when pulled from a binary is prefixed by go
|
||||||
|
version = strings.TrimPrefix(version, "go")
|
||||||
|
|
||||||
|
// we also need to trim starting from the first +<metadata> to
|
||||||
|
// correctly extract potential rc candidate information for cpe generation
|
||||||
|
// ex: 2.0.0-rc.1+build.123 -> 2.0.0-rc.1; if no + is found then + is returned
|
||||||
|
after, _, found := strings.Cut("+", version)
|
||||||
|
if found {
|
||||||
|
version = after
|
||||||
|
}
|
||||||
|
|
||||||
|
// extracting <version> and <candidate>
|
||||||
|
// https://regex101.com/r/985GsI/1
|
||||||
|
captureGroups := internal.MatchNamedCaptureGroups(versionCandidateGroups, version)
|
||||||
|
vr, ok := captureGroups["version"]
|
||||||
|
if !ok || vr == "" {
|
||||||
|
return stdlibCpe, fmt.Errorf("could not match candidate version for: %s", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
cpeString := fmt.Sprintf("cpe:2.3:a:golang:go:%s:-:*:*:*:*:*:*", captureGroups["version"])
|
||||||
|
if candidate, ok := captureGroups["candidate"]; ok && candidate != "" {
|
||||||
|
cpeString = fmt.Sprintf("cpe:2.3:a:golang:go:%s:%s:*:*:*:*:*:*", vr, candidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cpe.New(cpeString, cpe.GeneratedSource)
|
||||||
|
}
|
||||||
137
syft/pkg/cataloger/golang/stdlib_package_test.go
Normal file
137
syft/pkg/cataloger/golang/stdlib_package_test.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package golang
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/cmptest"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/cpe"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_stdlibPackageAndRelationships(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pkgs []pkg.Package
|
||||||
|
wantPkgs int
|
||||||
|
wantRels int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no packages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore non-go-binary packages",
|
||||||
|
pkgs: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "not-go",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Metadata: pkg.GolangModuleEntry{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantPkgs: 0,
|
||||||
|
wantRels: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with go-binary packages -- missing location",
|
||||||
|
pkgs: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "github.com/something/go",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||||
|
GoCompiledVersion: "go1.22.2",
|
||||||
|
MainModule: "github.com/something/go",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantPkgs: 0,
|
||||||
|
wantRels: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with go-binary packages",
|
||||||
|
pkgs: []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "github.com/something/go",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation("/bin/my-app")),
|
||||||
|
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||||
|
GoCompiledVersion: "go1.22.2",
|
||||||
|
MainModule: "github.com/something/go",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantPkgs: 1,
|
||||||
|
wantRels: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotPkgs, gotRels := stdlibPackageAndRelationships(tt.pkgs)
|
||||||
|
assert.Len(t, gotPkgs, tt.wantPkgs)
|
||||||
|
assert.Len(t, gotRels, tt.wantRels)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_stdlibPackageAndRelationships_values(t *testing.T) {
|
||||||
|
loc := file.NewLocation("/bin/my-app")
|
||||||
|
locSet := file.NewLocationSet(loc)
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: "github.com/something/go",
|
||||||
|
Version: "1.0.0",
|
||||||
|
Locations: locSet,
|
||||||
|
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||||
|
GoCompiledVersion: "go1.22.2",
|
||||||
|
MainModule: "github.com/something/go",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.SetID()
|
||||||
|
|
||||||
|
expectedPkg := pkg.Package{
|
||||||
|
Name: "stdlib",
|
||||||
|
Version: "go1.22.2",
|
||||||
|
PURL: packageURL("stdlib", "1.22.2"),
|
||||||
|
Language: pkg.Go,
|
||||||
|
Type: pkg.GoModulePkg,
|
||||||
|
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
|
||||||
|
CPEs: []cpe.CPE{
|
||||||
|
{
|
||||||
|
Attributes: cpe.MustAttributes("cpe:2.3:a:golang:go:1.22.2:-:*:*:*:*:*:*"),
|
||||||
|
Source: "syft-generated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Locations: locSet,
|
||||||
|
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||||
|
GoCompiledVersion: "go1.22.2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPkg.SetID()
|
||||||
|
|
||||||
|
expectedRel := artifact.Relationship{
|
||||||
|
From: expectedPkg,
|
||||||
|
To: p,
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotPkgs, gotRels := stdlibPackageAndRelationships([]pkg.Package{p})
|
||||||
|
require.Len(t, gotPkgs, 1)
|
||||||
|
|
||||||
|
gotPkg := gotPkgs[0]
|
||||||
|
if d := cmp.Diff(expectedPkg, gotPkg, cmptest.DefaultCommonOptions()...); d != "" {
|
||||||
|
t.Errorf("unexpected package (-want +got): %s", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, gotRels, 1)
|
||||||
|
gotRel := gotRels[0]
|
||||||
|
|
||||||
|
if d := cmp.Diff(expectedRel, gotRel, cmptest.DefaultCommonOptions()...); d != "" {
|
||||||
|
t.Errorf("unexpected relationship (-want +got): %s", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
syft/pkg/cataloger/golang/test-fixtures/image-not-a-module/.gitignore
vendored
Normal file
1
syft/pkg/cataloger/golang/test-fixtures/image-not-a-module/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/run-me
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
FROM --platform=linux/amd64 golang:1.22 AS builder
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY main.go main.go
|
||||||
|
|
||||||
|
# building with "." vs "main.go" is a difference in the buildinfo section
|
||||||
|
# specifically with main.go the buildinfo section will contain the following:
|
||||||
|
#
|
||||||
|
# path command-line-arguments
|
||||||
|
#
|
||||||
|
# instead of
|
||||||
|
#
|
||||||
|
# mod anchore.io/not/real
|
||||||
|
#
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o run-me main.go
|
||||||
|
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=builder /app/run-me /run-me
|
||||||
|
ENTRYPOINT ["/run-me"]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
module anchore.io/not/real
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
|
|
||||||
|
require github.com/mholt/archiver/v3 v3.5.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.1 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||||
|
github.com/golang/snappy v0.0.2 // indirect
|
||||||
|
github.com/klauspost/compress v1.11.4 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
|
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
|
)
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||||
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
||||||
|
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
|
||||||
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||||
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||||
|
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
|
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||||
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
||||||
|
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/mholt/archiver/v3"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
z := archiver.Zip{
|
||||||
|
MkdirAll: true,
|
||||||
|
SelectiveCompression: true,
|
||||||
|
ContinueOnError: false,
|
||||||
|
OverwriteExisting: false,
|
||||||
|
ImplicitTopLevelFolder: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := z.Archive([]string{"main.go"}, "test.zip")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
syft/pkg/cataloger/golang/test-fixtures/image-small/.gitignore
vendored
Normal file
1
syft/pkg/cataloger/golang/test-fixtures/image-small/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/run-me
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
FROM --platform=linux/amd64 golang:1.22 AS builder
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY main.go main.go
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o run-me .
|
||||||
|
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=builder /app/run-me /run-me
|
||||||
|
ENTRYPOINT ["/run-me"]
|
||||||
17
syft/pkg/cataloger/golang/test-fixtures/image-small/go.mod
Normal file
17
syft/pkg/cataloger/golang/test-fixtures/image-small/go.mod
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module anchore.io/not/real
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
|
|
||||||
|
require github.com/mholt/archiver/v3 v3.5.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.1 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||||
|
github.com/golang/snappy v0.0.2 // indirect
|
||||||
|
github.com/klauspost/compress v1.11.4 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
|
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.9 // indirect
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||||
|
)
|
||||||
26
syft/pkg/cataloger/golang/test-fixtures/image-small/go.sum
Normal file
26
syft/pkg/cataloger/golang/test-fixtures/image-small/go.sum
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||||
|
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
||||||
|
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
|
||||||
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||||
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||||
|
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||||
|
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||||
|
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
|
||||||
|
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
19
syft/pkg/cataloger/golang/test-fixtures/image-small/main.go
Normal file
19
syft/pkg/cataloger/golang/test-fixtures/image-small/main.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/mholt/archiver/v3"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
z := archiver.Zip{
|
||||||
|
MkdirAll: true,
|
||||||
|
SelectiveCompression: true,
|
||||||
|
ContinueOnError: false,
|
||||||
|
OverwriteExisting: false,
|
||||||
|
ImplicitTopLevelFolder: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := z.Archive([]string{"main.go"}, "test.zip")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,10 @@ package pkgtest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -40,6 +42,7 @@ type CatalogTester struct {
|
|||||||
compareOptions []cmp.Option
|
compareOptions []cmp.Option
|
||||||
locationComparer cmptest.LocationComparer
|
locationComparer cmptest.LocationComparer
|
||||||
licenseComparer cmptest.LicenseComparer
|
licenseComparer cmptest.LicenseComparer
|
||||||
|
packageStringer func(pkg.Package) string
|
||||||
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
|
customAssertions []func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +51,7 @@ func NewCatalogTester() *CatalogTester {
|
|||||||
wantErr: require.NoError,
|
wantErr: require.NoError,
|
||||||
locationComparer: cmptest.DefaultLocationComparer,
|
locationComparer: cmptest.DefaultLocationComparer,
|
||||||
licenseComparer: cmptest.DefaultLicenseComparer,
|
licenseComparer: cmptest.DefaultLicenseComparer,
|
||||||
|
packageStringer: stringPackage,
|
||||||
ignoreUnfulfilledPathResponses: map[string][]string{
|
ignoreUnfulfilledPathResponses: map[string][]string{
|
||||||
"FilesByPath": {
|
"FilesByPath": {
|
||||||
// most catalogers search for a linux release, which will not be fulfilled in testing
|
// most catalogers search for a linux release, which will not be fulfilled in testing
|
||||||
@ -187,6 +191,23 @@ func (p *CatalogTester) Expects(pkgs []pkg.Package, relationships []artifact.Rel
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CatalogTester) WithPackageStringer(fn func(pkg.Package) string) *CatalogTester {
|
||||||
|
p.packageStringer = fn
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CatalogTester) ExpectsPackageStrings(expected []string) *CatalogTester {
|
||||||
|
return p.ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, _ []artifact.Relationship) {
|
||||||
|
diffPackages(t, expected, pkgs, p.packageStringer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CatalogTester) ExpectsRelationshipStrings(expected []string) *CatalogTester {
|
||||||
|
return p.ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||||
|
diffRelationships(t, expected, relationships, pkgs, p.packageStringer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (p *CatalogTester) ExpectsResolverPathResponses(locations []string) *CatalogTester {
|
func (p *CatalogTester) ExpectsResolverPathResponses(locations []string) *CatalogTester {
|
||||||
p.expectedPathResponses = locations
|
p.expectedPathResponses = locations
|
||||||
return p
|
return p
|
||||||
@ -347,3 +368,70 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
|||||||
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
|
t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func diffPackages(t *testing.T, expected []string, actual []pkg.Package, pkgStringer func(pkg.Package) string) {
|
||||||
|
t.Helper()
|
||||||
|
sort.Strings(expected)
|
||||||
|
if d := cmp.Diff(expected, stringPackages(actual, pkgStringer)); d != "" {
|
||||||
|
t.Errorf("unexpected package strings (-want, +got): %s", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship, pkgs []pkg.Package, pkgStringer func(pkg.Package) string) {
|
||||||
|
t.Helper()
|
||||||
|
pkgsByID := make(map[artifact.ID]pkg.Package)
|
||||||
|
for _, p := range pkgs {
|
||||||
|
pkgsByID[p.ID()] = p
|
||||||
|
}
|
||||||
|
sort.Strings(expected)
|
||||||
|
if d := cmp.Diff(expected, stringRelationships(actual, pkgsByID, pkgStringer)); d != "" {
|
||||||
|
t.Errorf("unexpected relationship strings (-want, +got): %s", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringRelationships(relationships []artifact.Relationship, nameLookup map[artifact.ID]pkg.Package, pkgStringer func(pkg.Package) string) []string {
|
||||||
|
var result []string
|
||||||
|
for _, r := range relationships {
|
||||||
|
var fromName, toName string
|
||||||
|
{
|
||||||
|
fromPkg, ok := nameLookup[r.From.ID()]
|
||||||
|
if !ok {
|
||||||
|
fromName = string(r.From.ID())
|
||||||
|
} else {
|
||||||
|
fromName = pkgStringer(fromPkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
toPkg, ok := nameLookup[r.To.ID()]
|
||||||
|
if !ok {
|
||||||
|
toName = string(r.To.ID())
|
||||||
|
} else {
|
||||||
|
toName = pkgStringer(toPkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, fromName+" ["+string(r.Type)+"] "+toName)
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringPackages(pkgs []pkg.Package, pkgStringer func(pkg.Package) string) []string {
|
||||||
|
var result []string
|
||||||
|
for _, p := range pkgs {
|
||||||
|
result = append(result, pkgStringer(p))
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringPackage(p pkg.Package) string {
|
||||||
|
locs := p.Locations.ToSlice()
|
||||||
|
var loc string
|
||||||
|
if len(locs) > 0 {
|
||||||
|
loc = p.Locations.ToSlice()[0].RealPath
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s @ %s (%s)", p.Name, p.Version, loc)
|
||||||
|
}
|
||||||
|
|||||||
@ -4,13 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
@ -482,52 +479,20 @@ func Test_PackageCataloger_SitePackageRelationships(t *testing.T) {
|
|||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
pkgtest.NewCatalogTester().
|
pkgtest.NewCatalogTester().
|
||||||
WithImageResolver(t, test.fixture).
|
WithImageResolver(t, test.fixture).
|
||||||
ExpectsAssertion(func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
WithPackageStringer(stringPackage).
|
||||||
diffRelationships(t, test.expectedRelationships, relationships, pkgs)
|
ExpectsRelationshipStrings(test.expectedRelationships).
|
||||||
}).
|
|
||||||
TestCataloger(t, NewInstalledPackageCataloger())
|
TestCataloger(t, NewInstalledPackageCataloger())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func diffRelationships(t *testing.T, expected []string, actual []artifact.Relationship, pkgs []pkg.Package) {
|
func stringPackage(p pkg.Package) string {
|
||||||
pkgsByID := make(map[artifact.ID]pkg.Package)
|
locs := p.Locations.ToSlice()
|
||||||
for _, p := range pkgs {
|
var loc string
|
||||||
pkgsByID[p.ID()] = p
|
if len(locs) > 0 {
|
||||||
}
|
// we want the location of the site-packages, not the metadata file
|
||||||
sort.Strings(expected)
|
loc = path.Dir(path.Dir(p.Locations.ToSlice()[0].RealPath))
|
||||||
if d := cmp.Diff(expected, stringRelationships(actual, pkgsByID)); d != "" {
|
|
||||||
t.Errorf("unexpected relationships (-want, +got): %s", d)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return fmt.Sprintf("%s @ %s (%s)", p.Name, p.Version, loc)
|
||||||
func stringRelationships(relationships []artifact.Relationship, nameLookup map[artifact.ID]pkg.Package) []string {
|
|
||||||
var result []string
|
|
||||||
for _, r := range relationships {
|
|
||||||
var fromName, toName string
|
|
||||||
{
|
|
||||||
fromPkg, ok := nameLookup[r.From.ID()]
|
|
||||||
if !ok {
|
|
||||||
fromName = string(r.From.ID())
|
|
||||||
} else {
|
|
||||||
loc := path.Dir(path.Dir(fromPkg.Locations.ToSlice()[0].RealPath))
|
|
||||||
fromName = fmt.Sprintf("%s @ %s (%s)", fromPkg.Name, fromPkg.Version, loc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
toPkg, ok := nameLookup[r.To.ID()]
|
|
||||||
if !ok {
|
|
||||||
toName = string(r.To.ID())
|
|
||||||
} else {
|
|
||||||
loc := path.Dir(path.Dir(toPkg.Locations.ToSlice()[0].RealPath))
|
|
||||||
toName = fmt.Sprintf("%s @ %s (%s)", toPkg.Name, toPkg.Version, loc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, fromName+" ["+string(r.Type)+"] "+toName)
|
|
||||||
}
|
|
||||||
sort.Strings(result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user