Skip to content

Commit

Permalink
chore: refactor pom cataloger to scan and index all poms in the resolver
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Zantow <[email protected]>
  • Loading branch information
kzantow committed Jul 23, 2024
1 parent 6472bdf commit a1fb9d7
Show file tree
Hide file tree
Showing 14 changed files with 363 additions and 625 deletions.
5 changes: 5 additions & 0 deletions syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ func TestFileParser(t *testing.T, fixturePath string, parser generic.Parser, exp
NewCatalogTester().FromFile(t, fixturePath).Expects(expectedPkgs, expectedRelationships).TestParser(t, parser)
}

func TestCataloger(t *testing.T, fixtureDir string, cataloger pkg.Cataloger, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
NewCatalogTester().FromDirectory(t, fixtureDir).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}

func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Parser, env *generic.Environment, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()

Expand Down
7 changes: 4 additions & 3 deletions syft/pkg/cataloger/java/archive_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/vifraa/gopom"

"github.com/anchore/syft/internal"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
Expand Down Expand Up @@ -51,7 +52,7 @@ type archiveParser struct {
fileInfo archiveFilename
detectNested bool
cfg ArchiveCatalogerConfig
maven mavenResolver
maven *mavenResolver
}

type genericArchiveParserAdapter struct {
Expand Down Expand Up @@ -382,7 +383,7 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(ctx context.Context, paren
parsedPom = proj
}

pkgFromPom := newPackageFromMavenData(ctx, &j.maven, propertiesObj, parsedPom, parentPkg, j.location)
pkgFromPom := newPackageFromMavenData(ctx, j.maven, propertiesObj, parsedPom, parentPkg, j.location)
if pkgFromPom != nil {
pkgs = append(pkgs, *pkgFromPom)
}
Expand All @@ -396,7 +397,7 @@ func getDigestsFromArchive(archivePath string) ([]file.Digest, error) {
if err != nil {
return nil, fmt.Errorf("unable to open archive path (%s): %w", archivePath, err)
}
defer archiveCloser.Close()
defer internal.CloseAndLogError(archiveCloser, archivePath)

// grab and assign digest for the entire archive
digests, err := intFile.NewDigestsFromFile(archiveCloser, javaArchiveHashes)
Expand Down
9 changes: 5 additions & 4 deletions syft/pkg/cataloger/java/archive_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/vifraa/gopom"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
Expand Down Expand Up @@ -54,13 +55,13 @@ func generateMockMavenHandler(responseFixture string) func(w http.ResponseWriter
// Set the Content-Type header to indicate that the response is XML
w.Header().Set("Content-Type", "application/xml")
// Copy the file's content to the response writer
file, err := os.Open(responseFixture)
f, err := os.Open(responseFixture)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
_, err = io.Copy(w, file)
defer internal.CloseAndLogError(f, responseFixture)
_, err = io.Copy(w, f)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down Expand Up @@ -1081,7 +1082,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
test.expectedParent.Locations = locations

r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
actualPackage := newPackageFromMavenData(context.Background(), &r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
actualPackage := newPackageFromMavenData(context.Background(), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
if test.expectedPackage == nil {
require.Nil(t, actualPackage)
} else {
Expand Down
7 changes: 3 additions & 4 deletions syft/pkg/cataloger/java/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ func NewArchiveCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
// NewPomCataloger returns a cataloger capable of parsing dependencies from a pom.xml file.
// Pom files list dependencies that maybe not be locally installed yet.
func NewPomCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
gap := newGenericArchiveParserAdapter(cfg)

return generic.NewCataloger("java-pom-cataloger").
WithParserByGlobs(gap.parsePomXML, "**/pom.xml")
return pomXMLCataloger{
cfg: cfg,
}
}

// NewGradleLockfileCataloger returns a cataloger capable of parsing dependencies from a gradle.lockfile file.
Expand Down
4 changes: 2 additions & 2 deletions syft/pkg/cataloger/java/maven_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ type mavenResolver struct {

// newMavenResolver constructs a new mavenResolver with the given configuration.
// NOTE: the fileResolver is optional and if provided will be used to resolve parent poms by relative path
func newMavenResolver(fileResolver file.Resolver, cfg ArchiveCatalogerConfig) mavenResolver {
return mavenResolver{
func newMavenResolver(fileResolver file.Resolver, cfg ArchiveCatalogerConfig) *mavenResolver {
return &mavenResolver{
cfg: cfg,
cache: cache.GetManager().GetCache("java/maven/repo", "v1"),
resolved: map[mavenID]*gopom.Project{},
Expand Down
2 changes: 1 addition & 1 deletion syft/pkg/cataloger/java/maven_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func Test_mavenResolverRemote(t *testing.T) {
}

func Test_relativePathParent(t *testing.T) {
resolver, err := fileresolver.NewFromDirectory("test-fixtures/pom/relative", "")
resolver, err := fileresolver.NewFromDirectory("test-fixtures/pom/local", "")
require.NoError(t, err)

r := newMavenResolver(resolver, DefaultArchiveCatalogerConfig())
Expand Down
63 changes: 54 additions & 9 deletions syft/pkg/cataloger/java/parse_pom_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,79 @@ import (
"github.com/vifraa/gopom"
"golang.org/x/net/html/charset"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"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"
)

const pomXMLGlob = "*pom.xml"

func (gap genericArchiveParserAdapter) parsePomXML(ctx context.Context, fileResolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pom, err := decodePomXML(reader)
if err != nil || pom == nil {
type pomXMLCataloger struct {
cfg ArchiveCatalogerConfig
}

func (p pomXMLCataloger) Name() string {
return "java-pom-cataloger"
}

func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
locations, err := fileResolver.FilesByGlob("**/pom.xml")
if err != nil {
return nil, nil, err
}

r := newMavenResolver(fileResolver, gap.cfg)
r.pomLocations[pom] = reader.Location // store the location this pom was resolved in order to attempt parent pom lookups
r := newMavenResolver(fileResolver, p.cfg)

var poms []*gopom.Project
for _, pomLocation := range locations {
pom, err := readPomFromLocation(fileResolver, pomLocation)
if err != nil || pom == nil {
log.Debugf("error while getting contents for: %v %v", pomLocation.RealPath, err)
continue
}

poms = append(poms, pom)

// store information about this pom for future lookups
r.pomLocations[pom] = pomLocation
r.resolved[newMavenIDFromPom(pom)] = pom
}

var pkgs []pkg.Package
for _, pom := range poms {
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
}
return pkgs, nil, nil
}

func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {
contents, err := fileResolver.FileContentsByLocation(pomLocation)
if err != nil {
return nil, err
}
defer internal.CloseAndLogError(contents, pomLocation.RealPath)

pom, err := decodePomXML(contents)
if err != nil || pom == nil {
return nil, err
}
return pom, nil
}

func processPomXML(ctx context.Context, r *mavenResolver, pom *gopom.Project, loc file.Location) []pkg.Package {
var pkgs []pkg.Package

for _, dep := range pomDependencies(pom) {
id := newMavenID(dep.GroupID, dep.ArtifactID, dep.Version)
log.Tracef("adding dependency to SBOM: %v", id)
p, err := newPackageFromDependency(
ctx,
&r,
r,
pom,
dep,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)
if err != nil {
log.Debugf("error adding dependency %v: %v", id, err)
Expand All @@ -51,7 +96,7 @@ func (gap genericArchiveParserAdapter) parsePomXML(ctx context.Context, fileReso
pkgs = append(pkgs, *p)
}

return pkgs, nil, nil
return pkgs
}

func newPomProject(ctx context.Context, r *mavenResolver, path string, pom *gopom.Project) *pkg.JavaPomProject {
Expand Down
52 changes: 26 additions & 26 deletions syft/pkg/cataloger/java/parse_pom_xml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import (

func Test_parsePomXML(t *testing.T) {
tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/pom.xml",
dir: "test-fixtures/pom/local/example-java-app-maven",
expected: []pkg.Package{
{
Name: "joda-time",
Expand Down Expand Up @@ -59,19 +59,19 @@ func Test_parsePomXML(t *testing.T) {
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}

gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
})

pkgtest.TestFileParser(t, test.input, gap.parsePomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
Expand Down Expand Up @@ -132,30 +132,30 @@ func Test_decodePomXML_surviveNonUtf8Encoding(t *testing.T) {

func Test_parseCommonsTextPomXMLProject(t *testing.T) {
tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/commons-text.pom.xml",
dir: "test-fixtures/pom/local/commons-text-1.10.0",

expected: getCommonsTextExpectedPackages(),
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}

gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
UseMavenLocalRepository: false,
})
pkgtest.TestFileParser(t, test.input, gap.parsePomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
Expand All @@ -180,22 +180,22 @@ func Test_parseCommonsTextPomXMLProjectWithLocalRepository(t *testing.T) {
}

tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/commons-text.pom.xml",
dir: "test-fixtures/pom/local/commons-text-1.10.0",
expected: expectedPackages,
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}

gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
Expand All @@ -204,7 +204,7 @@ func Test_parseCommonsTextPomXMLProjectWithLocalRepository(t *testing.T) {
MavenLocalRepositoryDir: "test-fixtures/pom/maven-repo",
MaxParentRecursiveDepth: 5,
})
pkgtest.TestFileParser(t, test.input, gap.parsePomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
Expand All @@ -231,22 +231,22 @@ func Test_parseCommonsTextPomXMLProjectWithNetwork(t *testing.T) {
}

tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/commons-text.pom.xml",
dir: "test-fixtures/pom/local/commons-text-1.10.0",
expected: expectedPackages,
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}

gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
Expand All @@ -256,7 +256,7 @@ func Test_parseCommonsTextPomXMLProjectWithNetwork(t *testing.T) {
UseMavenLocalRepository: false,
MaxParentRecursiveDepth: 5,
})
pkgtest.TestFileParser(t, test.input, gap.parsePomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
Expand Down Expand Up @@ -334,7 +334,7 @@ func Test_parsePomXMLProject(t *testing.T) {
pom, err := gopom.ParseFromReader(fixture)
require.NoError(t, err)

actual := newPomProject(context.Background(), &r, fixture.Name(), pom)
actual := newPomProject(context.Background(), r, fixture.Name(), pom)
assert.NoError(t, err)
assert.Equal(t, test.project, actual)

Expand Down Expand Up @@ -400,7 +400,7 @@ func Test_pomParent(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
assert.Equal(t, test.expected, pomParent(context.Background(), &r, &gopom.Project{Parent: test.input}))
assert.Equal(t, test.expected, pomParent(context.Background(), r, &gopom.Project{Parent: test.input}))
})
}
}
Expand Down
Loading

0 comments on commit a1fb9d7

Please sign in to comment.