mirror of
https://github.com/anchore/syft.git
synced 2026-08-19 08:38:25 +02:00
Avoid duplicate packages for replaced Go modules (#5069)
* Prevent duplicate Go packages after source replacement resolution Source analysis already applies module replacements through go/packages. Avoid synthesizing the same replacement again from go.mod while retaining fallback synthesis for modules that source analysis did not resolve. Constraint: Preserve unimported and local-path replacement cataloging. Rejected: Deduplicate only during final assembly | That retains redundant license lookup and ambiguous metadata ownership. Confidence: high Scope-risk: narrow Directive: Keep go.mod fallback packages limited to modules absent from source analysis. Tested: Focused replacement regression, related Go module parser tests, go vet, gofmt, and diff checks. Not-tested: Docker-backed full cataloger fixtures; local root storage was exhausted by image generation. Signed-off-by: ychampion <ychampion@users.noreply.github.com> * Keep replacement fixtures with the Go module test data Constraint: The maintainer reserves internal/gotestdata for fixtures that need special Go tooling discovery. Rejected: Leave this fixture in gotestdata | The regression opens its module explicitly and does not need the special location. Confidence: high Scope-risk: narrow Directive: Use internal/gotestdata only when a fixture must avoid Go testdata discovery rules. Tested: replacement regression repeated 10 times; Go module parser table; go vet for the Go cataloger; gofmt; diff checks. Not-tested: Full cataloger package; three unrelated parser fixtures fail identically on exact prior head in this environment. Signed-off-by: ychampion <ychampion@users.noreply.github.com> --------- Signed-off-by: ychampion <ychampion@users.noreply.github.com> Co-authored-by: ychampion <ychampion@users.noreply.github.com>
This commit is contained in:
parent
22ffd5209d
commit
289137ff3c
@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
|
||||
|
||||
// only use go.mod packages NOT found in source analysis
|
||||
goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests)
|
||||
c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests)
|
||||
c.applyReplaceDirectives(ctx, resolver, modFile, sourceModules, goModPackages, reader, digests)
|
||||
c.applyExcludeDirectives(modFile, goModPackages)
|
||||
|
||||
pkgs = c.assembleResults(catalogedModules, goModPackages)
|
||||
@ -365,8 +365,11 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file.
|
||||
}
|
||||
|
||||
// applyReplaceDirectives processes replace directives from go.mod
|
||||
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
|
||||
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, sourceModules map[string]*packages.Module, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
|
||||
for _, m := range modFile.Replace {
|
||||
if sourceModules != nil && sourceModules[m.Old.Path] != nil {
|
||||
continue
|
||||
}
|
||||
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
|
||||
var finalPath string
|
||||
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {
|
||||
|
||||
44
syft/pkg/cataloger/golang/parse_go_mod_replace_test.go
Normal file
44
syft/pkg/cataloger/golang/parse_go_mod_replace_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
stereofile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
)
|
||||
|
||||
func Test_parseGoSource_replacedModulesAreNotDuplicated(t *testing.T) {
|
||||
fixture := filepath.Join("testdata", "go-source-replacements")
|
||||
s, err := directorysource.NewFromPath(fixture)
|
||||
require.NoError(t, err)
|
||||
resolver, err := s.FileResolver(source.AllLayersScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
modPath, err := filepath.Abs(filepath.Join(fixture, "go.mod"))
|
||||
require.NoError(t, err)
|
||||
contents, err := os.Open(modPath)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, contents.Close()) })
|
||||
|
||||
reader := file.LocationReadCloser{
|
||||
Location: file.NewVirtualLocationFromDirectory("go.mod", "go.mod", *stereofile.NewFileReference(stereofile.Path(modPath))),
|
||||
ReadCloser: contents,
|
||||
}
|
||||
config := DefaultCatalogerConfig().WithUsePackagesLib(true).WithSearchRemoteLicenses(false)
|
||||
pkgs, _, err := newGoModCataloger(config).parseGoModFile(pkgtest.Context(t), resolver, nil, reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
versions := make(map[string][]string)
|
||||
for _, p := range pkgs {
|
||||
versions[p.Name] = append(versions[p.Name], p.Version)
|
||||
}
|
||||
require.Equal(t, []string{"v1.5.2"}, versions["rsc.io/quote"])
|
||||
require.Equal(t, []string{"v1.3.1"}, versions["rsc.io/sampler"])
|
||||
}
|
||||
15
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod
vendored
Normal file
15
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.mod
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
module example.com/repro
|
||||
|
||||
go 1.23
|
||||
|
||||
require rsc.io/quote v1.5.1
|
||||
|
||||
require (
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
|
||||
rsc.io/sampler v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
rsc.io/quote => rsc.io/quote v1.5.2
|
||||
rsc.io/sampler => rsc.io/sampler v1.3.1
|
||||
)
|
||||
6
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum
vendored
Normal file
6
syft/pkg/cataloger/golang/testdata/go-source-replacements/go.sum
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
|
||||
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
|
||||
rsc.io/sampler v1.3.1 h1:F0c3J2nQCdk9ODsNhU3sElnvPIxM/xV1c/qZuAeZmac=
|
||||
rsc.io/sampler v1.3.1/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
11
syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go
vendored
Normal file
11
syft/pkg/cataloger/golang/testdata/go-source-replacements/main.go
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"rsc.io/quote"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(quote.Hello())
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user