better go mod detection from partial package builds (#3060)

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2024-07-24 09:34:40 -04:00 committed by GitHub
parent ca945d16e0
commit 9573f557d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 141 additions and 12 deletions

View File

@ -11,6 +11,7 @@ import (
"io"
"regexp"
"runtime/debug"
"slices"
"strings"
"time"
@ -97,17 +98,19 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.
return relationships
}
var emptyModule debug.Module
var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"}
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
if mod == nil {
return nil, pkgs
return nil, nil
}
var empty debug.Module
if mod.Main == empty && mod.Path != "" {
mod.Main = createMainModuleFromPath(mod.Path)
if missingMainModule(mod) {
mod.Main = createMainModuleFromPath(mod)
}
var pkgs []pkg.Package
for _, dep := range mod.Deps {
if dep == nil {
continue
@ -130,7 +133,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
}
}
if mod.Main == empty {
if mod.Main == emptyModule {
return nil, pkgs
}
@ -139,6 +142,16 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file
return &main, pkgs
}
func missingMainModule(mod *extendedBuildInfo) bool {
if mod.Main == emptyModule && mod.Path != "" {
return true
}
// special case: when invoking go build with a source file and not a package (directory) then you will
// see "command-line-arguments" as the main module path... even though that's not the main module. In this
// circumstance, we should treat the main module as missing and search for it within the dependencies.
return mod.Main == moduleFromPartialPackageBuild
}
func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
gbs := getBuildSettings(mod.Settings)
gover, experiments := getExperimentsFromVersion(mod.GoVersion)
@ -358,8 +371,29 @@ func getExperimentsFromVersion(version string) (string, []string) {
return version, experiments
}
func createMainModuleFromPath(path string) (mod debug.Module) {
mod.Path = path
mod.Version = devel
return
func createMainModuleFromPath(existing *extendedBuildInfo) debug.Module {
// search for a main module candidate within the dependencies
var mainModuleCandidates []debug.Module
var usedIndex int
for i, dep := range existing.Deps {
if dep == nil {
continue
}
if dep.Version == devel {
usedIndex = i
mainModuleCandidates = append(mainModuleCandidates, *dep)
}
}
if len(mainModuleCandidates) == 1 {
// we need to prune the dependency from module list
existing.Deps = slices.Delete(existing.Deps, usedIndex, usedIndex+1)
return mainModuleCandidates[0]
}
// otherwise craft a main module from the path (a bit of a cop out, but allows us to have a main module)
return debug.Module{
Path: existing.Path,
Version: devel,
}
}

View File

@ -210,7 +210,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
},
{
name: "buildGoPkgInfo parses a blank mod and returns no packages",
mod: &extendedBuildInfo{&debug.BuildInfo{}, nil, ""},
mod: &extendedBuildInfo{BuildInfo: &debug.BuildInfo{}, cryptoSettings: nil, arch: ""},
expected: []pkg.Package(nil),
},
{
@ -946,6 +946,95 @@ func TestBuildGoPkgInfo(t *testing.T) {
},
}},
},
{
name: "parse a mod from path (partial build of package)",
mod: &extendedBuildInfo{
BuildInfo: &debug.BuildInfo{
GoVersion: "go1.22.2",
Main: debug.Module{Path: "command-line-arguments"},
Settings: []debug.BuildSetting{
{
Key: "-ldflags",
Value: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
},
{Key: "GOARCH", Value: archDetails},
{Key: "GOOS", Value: "darwin"},
{Key: "GOAMD64", Value: "v1"},
},
Deps: []*debug.Module{
{
Path: "github.com/kuskoman/something-else",
Version: "v1.2.3",
},
{
Path: "github.com/kuskoman/logstash-exporter",
Version: "(devel)",
},
},
},
arch: archDetails,
},
expected: []pkg.Package{
{
Name: "github.com/kuskoman/something-else",
Language: pkg.Go,
Type: pkg.GoModulePkg,
Version: "v1.2.3",
PURL: "pkg:golang/github.com/kuskoman/something-else@v1.2.3",
Locations: file.NewLocationSet(
file.NewLocationFromCoordinates(
file.Coordinates{
RealPath: "/a-path",
FileSystemID: "layer-id",
},
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
Architecture: archDetails,
MainModule: "github.com/kuskoman/logstash-exporter", // correctly attached the main module
},
},
{
Name: "github.com/kuskoman/logstash-exporter",
Language: pkg.Go,
Type: pkg.GoModulePkg,
Version: "v1.7.0",
PURL: "pkg:golang/github.com/kuskoman/logstash-exporter@v1.7.0",
Locations: file.NewLocationSet(
file.NewLocationFromCoordinates(
file.Coordinates{
RealPath: "/a-path",
FileSystemID: "layer-id",
},
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
Metadata: pkg.GolangBinaryBuildinfoEntry{
GoCompiledVersion: "go1.22.2",
BuildSettings: []pkg.KeyValue{
{
Key: "-ldflags",
Value: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
},
{
Key: "GOARCH",
Value: "amd64",
},
{
Key: "GOOS",
Value: "darwin",
},
{
Key: "GOAMD64",
Value: "v1",
},
},
Architecture: archDetails,
MainModule: "github.com/kuskoman/logstash-exporter",
},
},
},
},
}
for _, test := range tests {
@ -1092,6 +1181,12 @@ func Test_extractVersionFromLDFlags(t *testing.T) {
wantMajorVersion: "6",
wantFullVersion: "v6.1.7",
},
{
name: "logstash-exporter",
ldflags: `build -ldflags="-w -s -X github.com/kuskoman/logstash-exporter/config.Version=v1.7.0 -X github.com/kuskoman/logstash-exporter/config.GitCommit=db696dbcfe5a91d288d5ad44ce8ccbea97e65978 -X github.com/kuskoman/logstash-exporter/config.BuildDate=2024-07-17T08:12:17Z"`,
wantMajorVersion: "1",
wantFullVersion: "v1.7.0",
},
//////////////////////////////////////////////////////////////////
// negative cases
{

View File

@ -56,7 +56,7 @@ func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildI
}
}
builds = append(builds, &extendedBuildInfo{bi, v, arch})
builds = append(builds, &extendedBuildInfo{BuildInfo: bi, cryptoSettings: v, arch: arch})
}
return builds
}