Skip to content

Commit

Permalink
chore: improve property resolution for boms
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Zantow <kzantow@gmail.com>
  • Loading branch information
kzantow committed Jul 23, 2024
1 parent a1fb9d7 commit 7b2fb7a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 74 deletions.
6 changes: 3 additions & 3 deletions syft/pkg/cataloger/java/archive_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,13 +334,13 @@ func (j *archiveParser) discoverMainPackageFromPomInfo(ctx context.Context) (gro
if parsedPom != nil && parsedPom.project != nil {
pom := parsedPom.project
if group == "" {
group = j.maven.getPropertyValue(ctx, pom, pom.GroupID)
group = j.maven.getPropertyValue(ctx, pom.GroupID, pom)
}
if name == "" {
name = j.maven.getPropertyValue(ctx, pom, pom.ArtifactID)
name = j.maven.getPropertyValue(ctx, pom.ArtifactID, pom)
}
if version == "" {
version = j.maven.getPropertyValue(ctx, pom, pom.Version)
version = j.maven.getPropertyValue(ctx, pom.Version, pom)
}
}

Expand Down
102 changes: 49 additions & 53 deletions syft/pkg/cataloger/java/maven_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ func newMavenIDFromPom(pom *gopom.Project) mavenID {
return newMavenID(pom.GroupID, pom.ArtifactID, pom.Version)
}

// Valid indicates this mavenID has non-empty groupId, artifactId, and version
func (m mavenID) Valid() bool {
return m.GroupID != "" && m.ArtifactID != "" && m.Version != ""
}

func (m mavenID) String() string {
return fmt.Sprintf("(groupId: %s artifactId: %s version: %s)", m.GroupID, m.ArtifactID, m.Version)
}
Expand Down Expand Up @@ -90,11 +85,11 @@ func newMavenResolver(fileResolver file.Resolver, cfg ArchiveCatalogerConfig) *m
// Properties which are not resolved result in empty string ""
//
//nolint:gocognit
func (r *mavenResolver) getPropertyValue(ctx context.Context, pom *gopom.Project, propertyValue *string) string {
func (r *mavenResolver) getPropertyValue(ctx context.Context, propertyValue *string, resolutionContext ...*gopom.Project) string {
if propertyValue == nil {
return ""
}
resolved, err := r.resolveExpression(ctx, pom, *propertyValue, nil)
resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, nil)
if err != nil {
log.Debugf("error resolving maven property: %s: %v", *propertyValue, err)
return ""
Expand All @@ -105,11 +100,11 @@ func (r *mavenResolver) getPropertyValue(ctx context.Context, pom *gopom.Project
// resolveExpression resolves an expression, which may be a plain string or a string with ${ property.references }
//
//nolint:gocognit
func (r *mavenResolver) resolveExpression(ctx context.Context, pom *gopom.Project, expression string, resolving []string) (string, error) {
func (r *mavenResolver) resolveExpression(ctx context.Context, resolutionContext []*gopom.Project, expression string, resolving []string) (string, error) {
var err error
return expressionMatcher.ReplaceAllStringFunc(expression, func(match string) string {
propertyExpression := strings.TrimSpace(match[2 : len(match)-1]) // remove leading ${ and trailing }
resolved, e := r.resolveProperty(ctx, pom, propertyExpression, resolving)
resolved, e := r.resolveProperty(ctx, resolutionContext, propertyExpression, resolving)
if e != nil {
err = errors.Join(err, e)
return ""
Expand All @@ -121,31 +116,33 @@ func (r *mavenResolver) resolveExpression(ctx context.Context, pom *gopom.Projec
// resolveProperty resolves properties recursively from the root project
//
//nolint:gocognit
func (r *mavenResolver) resolveProperty(ctx context.Context, pom *gopom.Project, propertyExpression string, resolving []string) (string, error) {
func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext []*gopom.Project, propertyExpression string, resolving []string) (string, error) {
// prevent cycles
if slices.Contains(resolving, propertyExpression) {
return "", fmt.Errorf("cycle detected resolving: %s", propertyExpression)
}
resolving = append(resolving, propertyExpression)

value, err := r.resolveProjectProperty(ctx, pom, propertyExpression, resolving)
if err != nil {
return value, err
}
if value != "" {
return value, nil
}
for _, pom := range resolutionContext {
value, err := r.resolveProjectProperty(ctx, resolutionContext, pom, propertyExpression, resolving)
if err != nil {
return value, err
}
if value != "" {
return value, nil
}

current := pom
for current != nil {
if current.Properties != nil && current.Properties.Entries != nil {
if value, ok := current.Properties.Entries[propertyExpression]; ok {
return r.resolveExpression(ctx, pom, value, resolving) // property values can contain expressions
current := pom
for current != nil {
if current.Properties != nil && current.Properties.Entries != nil {
if value, ok := current.Properties.Entries[propertyExpression]; ok {
return r.resolveExpression(ctx, resolutionContext, value, resolving) // property values can contain expressions
}
}
current, err = r.resolveParent(ctx, current)
if err != nil {
return "", err
}
}
current, err = r.resolveParent(ctx, current)
if err != nil {
return "", err
}
}

Expand All @@ -155,7 +152,7 @@ func (r *mavenResolver) resolveProperty(ctx context.Context, pom *gopom.Project,
// resolveProjectProperty resolves properties on the project
//
//nolint:gocognit
func (r *mavenResolver) resolveProjectProperty(ctx context.Context, pom *gopom.Project, propertyExpression string, resolving []string) (string, error) {
func (r *mavenResolver) resolveProjectProperty(ctx context.Context, resolutionContext []*gopom.Project, pom *gopom.Project, propertyExpression string, resolving []string) (string, error) {
// see if we have a project.x expression and process this based
// on the xml tags in gopom
parts := strings.Split(propertyExpression, ".")
Expand Down Expand Up @@ -197,7 +194,7 @@ func (r *mavenResolver) resolveProjectProperty(ctx context.Context, pom *gopom.P
// If this was the last part of the property name, return the value
if partNum == numParts-1 {
value := fmt.Sprintf("%v", pomValue.Interface())
return r.resolveExpression(ctx, pom, value, resolving)
return r.resolveExpression(ctx, resolutionContext, value, resolving)
}
break
}
Expand Down Expand Up @@ -356,9 +353,9 @@ func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project) (
parent := pom.Parent
pomWithoutParent := *pom
pomWithoutParent.Parent = nil
groupID := r.getPropertyValue(ctx, &pomWithoutParent, parent.GroupID)
artifactID := r.getPropertyValue(ctx, &pomWithoutParent, parent.ArtifactID)
version := r.getPropertyValue(ctx, &pomWithoutParent, parent.Version)
groupID := r.getPropertyValue(ctx, parent.GroupID, &pomWithoutParent)
artifactID := r.getPropertyValue(ctx, parent.ArtifactID, &pomWithoutParent)
version := r.getPropertyValue(ctx, parent.Version, &pomWithoutParent)

// check cache before resolving
parentID := mavenID{groupID, artifactID, version}
Expand All @@ -379,39 +376,38 @@ func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project) (
// findInheritedVersion attempts to find the version of a dependency (groupID, artifactID) by searching all parent poms and imported managed dependencies
//
//nolint:gocognit
func (r *mavenResolver) findInheritedVersion(ctx context.Context, root *gopom.Project, pom *gopom.Project, groupID, artifactID string, resolving ...mavenID) (string, error) {
id := newMavenID(pom.GroupID, pom.ArtifactID, pom.Version)
if len(resolving) >= r.cfg.MaxParentRecursiveDepth {
return "", fmt.Errorf("maximum depth reached attempting to resolve version for: %s:%s at: %v", groupID, artifactID, resolving)
func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Project, groupID, artifactID string, resolutionContext ...*gopom.Project) (string, error) {
if len(resolutionContext) >= r.cfg.MaxParentRecursiveDepth {
return "", fmt.Errorf("maximum depth reached attempting to resolve version for: %s:%s at: %v", groupID, artifactID, newMavenIDFromPom(pom))
}
if slices.Contains(resolving, id) {
return "", fmt.Errorf("cycle detected attempting to resolve version for: %s:%s at: %v", groupID, artifactID, resolving)
if slices.Contains(resolutionContext, pom) {
return "", fmt.Errorf("cycle detected attempting to resolve version for: %s:%s at: %v", groupID, artifactID, newMavenIDFromPom(pom))
}
resolving = append(resolving, id)
resolutionContext = append(resolutionContext, pom)

var err error
var version string

// check for entries in dependencyManagement first
for _, dep := range pomManagedDependencies(pom) {
depGroupID := r.getPropertyValue(ctx, root, dep.GroupID)
depArtifactID := r.getPropertyValue(ctx, root, dep.ArtifactID)
depGroupID := r.getPropertyValue(ctx, dep.GroupID, resolutionContext...)
depArtifactID := r.getPropertyValue(ctx, dep.ArtifactID, resolutionContext...)
if depGroupID == groupID && depArtifactID == artifactID {
version = r.getPropertyValue(ctx, root, dep.Version)
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
if version != "" {
return version, nil
}
}

// imported pom files should be treated just like parent poms, they are used to define versions of dependencies
if deref(dep.Type) == "pom" && deref(dep.Scope) == "import" {
depVersion := r.getPropertyValue(ctx, root, dep.Version)
depVersion := r.getPropertyValue(ctx, dep.Version, resolutionContext...)

depPom, err := r.findPom(ctx, depGroupID, depArtifactID, depVersion)
if err != nil {
return "", err
}
version, err = r.findInheritedVersion(ctx, root, depPom, groupID, artifactID, resolving...)
version, err = r.findInheritedVersion(ctx, depPom, groupID, artifactID, resolutionContext...)
if err != nil {
return "", err
}
Expand All @@ -427,7 +423,7 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, root *gopom.Pr
return "", err
}
if parent != nil {
version, err = r.findInheritedVersion(ctx, root, parent, groupID, artifactID, resolving...)
version, err = r.findInheritedVersion(ctx, parent, groupID, artifactID, resolutionContext...)
if err != nil {
return "", err
}
Expand All @@ -438,10 +434,10 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, root *gopom.Pr

// check for inherited dependencies
for _, dep := range pomDependencies(pom) {
depGroupID := r.getPropertyValue(ctx, root, dep.GroupID)
depArtifactID := r.getPropertyValue(ctx, root, dep.ArtifactID)
depGroupID := r.getPropertyValue(ctx, dep.GroupID, resolutionContext...)
depArtifactID := r.getPropertyValue(ctx, dep.ArtifactID, resolutionContext...)
if depGroupID == groupID && depArtifactID == artifactID {
version = r.getPropertyValue(ctx, root, dep.Version)
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
if version != "" {
return version, nil
}
Expand Down Expand Up @@ -490,8 +486,8 @@ func (r *mavenResolver) pomLicenses(ctx context.Context, pom *gopom.Project) []g
var out []gopom.License
for _, license := range deref(pom.Licenses) {
// if we find non-empty licenses, return them
name := r.getPropertyValue(ctx, pom, license.Name)
url := r.getPropertyValue(ctx, pom, license.URL)
name := r.getPropertyValue(ctx, license.Name, pom)
url := r.getPropertyValue(ctx, license.URL, pom)
if name != "" || url != "" {
out = append(out, license)
}
Expand All @@ -509,7 +505,7 @@ func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *go
if !hasPomLocation || pom == nil || pom.Parent == nil {
return nil
}
relativePath := r.getPropertyValue(ctx, pom, pom.Parent.RelativePath)
relativePath := r.getPropertyValue(ctx, pom.Parent.RelativePath, pom)
if relativePath == "" {
return nil
}
Expand All @@ -536,9 +532,9 @@ func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *go
return nil
}
// ensure ids match
groupID := r.getPropertyValue(ctx, pom, parentPom.GroupID)
artifactID := r.getPropertyValue(ctx, pom, parentPom.ArtifactID)
version := r.getPropertyValue(ctx, pom, parentPom.Version)
groupID := r.getPropertyValue(ctx, parentPom.GroupID, pom)
artifactID := r.getPropertyValue(ctx, parentPom.ArtifactID, pom)
version := r.getPropertyValue(ctx, parentPom.Version, pom)

newParentID := mavenID{groupID, artifactID, version}
if newParentID != parentID {
Expand Down
8 changes: 4 additions & 4 deletions syft/pkg/cataloger/java/maven_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func Test_resolveProperty(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
resolved := r.getPropertyValue(context.Background(), &test.pom, ptr(test.property))
resolved := r.getPropertyValue(context.Background(), ptr(test.property), &test.pom)
require.Equal(t, test.expected, resolved)
})
}
Expand Down Expand Up @@ -201,7 +201,7 @@ func Test_mavenResolverLocal(t *testing.T) {
} else {
require.NoError(t, err)
}
got := r.getPropertyValue(context.Background(), pom, &test.expression)
got := r.getPropertyValue(context.Background(), &test.expression, pom)
require.Equal(t, test.expected, got)
})
}
Expand Down Expand Up @@ -242,7 +242,7 @@ func Test_mavenResolverRemote(t *testing.T) {
} else {
require.NoError(t, err)
}
got := r.getPropertyValue(context.Background(), pom, &test.expression)
got := r.getPropertyValue(context.Background(), &test.expression, pom)
require.Equal(t, test.expected, got)
})
}
Expand Down Expand Up @@ -276,7 +276,7 @@ func Test_relativePathParent(t *testing.T) {
require.NoError(t, err)
require.Contains(t, r.pomLocations, parent)

got := r.getPropertyValue(ctx, pom, ptr("${commons-exec_subversion}"))
got := r.getPropertyValue(ctx, ptr("${commons-exec_subversion}"), pom)
require.Equal(t, "3", got)
}

Expand Down
28 changes: 14 additions & 14 deletions syft/pkg/cataloger/java/parse_pom_xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,38 +100,38 @@ func processPomXML(ctx context.Context, r *mavenResolver, pom *gopom.Project, lo
}

func newPomProject(ctx context.Context, r *mavenResolver, path string, pom *gopom.Project) *pkg.JavaPomProject {
artifactID := r.getPropertyValue(ctx, pom, pom.ArtifactID)
name := r.getPropertyValue(ctx, pom, pom.Name)
projectURL := r.getPropertyValue(ctx, pom, pom.URL)
artifactID := r.getPropertyValue(ctx, pom.ArtifactID, pom)
name := r.getPropertyValue(ctx, pom.Name, pom)
projectURL := r.getPropertyValue(ctx, pom.URL, pom)

log.WithFields("path", path, "artifactID", artifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
return &pkg.JavaPomProject{
Path: path,
Parent: pomParent(ctx, r, pom),
GroupID: r.getPropertyValue(ctx, pom, pom.GroupID),
GroupID: r.getPropertyValue(ctx, pom.GroupID, pom),
ArtifactID: artifactID,
Version: r.getPropertyValue(ctx, pom, pom.Version),
Version: r.getPropertyValue(ctx, pom.Version, pom),
Name: name,
Description: cleanDescription(r.getPropertyValue(ctx, pom, pom.Description)),
Description: cleanDescription(r.getPropertyValue(ctx, pom.Description, pom)),
URL: projectURL,
}
}

func newPackageFromDependency(ctx context.Context, r *mavenResolver, pom *gopom.Project, dep gopom.Dependency, locations ...file.Location) (*pkg.Package, error) {
groupID := r.getPropertyValue(ctx, pom, dep.GroupID)
artifactID := r.getPropertyValue(ctx, pom, dep.ArtifactID)
version := r.getPropertyValue(ctx, pom, dep.Version)
groupID := r.getPropertyValue(ctx, dep.GroupID, pom)
artifactID := r.getPropertyValue(ctx, dep.ArtifactID, pom)
version := r.getPropertyValue(ctx, dep.Version, pom)

var err error
if version == "" {
version, err = r.findInheritedVersion(ctx, pom, pom, groupID, artifactID)
version, err = r.findInheritedVersion(ctx, pom, groupID, artifactID)
}

m := pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: groupID,
ArtifactID: artifactID,
Scope: r.getPropertyValue(ctx, pom, dep.Scope),
Scope: r.getPropertyValue(ctx, dep.Scope, pom),
},
}

Expand Down Expand Up @@ -224,9 +224,9 @@ func pomParent(ctx context.Context, r *mavenResolver, pom *gopom.Project) *pkg.J
return nil
}

groupID := r.getPropertyValue(ctx, pom, pom.Parent.GroupID)
artifactID := r.getPropertyValue(ctx, pom, pom.Parent.ArtifactID)
version := r.getPropertyValue(ctx, pom, pom.Parent.Version)
groupID := r.getPropertyValue(ctx, pom.Parent.GroupID, pom)
artifactID := r.getPropertyValue(ctx, pom.Parent.ArtifactID, pom)
version := r.getPropertyValue(ctx, pom.Parent.Version, pom)

if groupID == "" && artifactID == "" && version == "" {
return nil
Expand Down

0 comments on commit 7b2fb7a

Please sign in to comment.