Alex Goodman 4a18895545
Add abstraction for adding relationships from package cataloger results (#2853)
* add internal dependency resolver

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor dependency relationship resolution to common object

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* replace cataloger decorator with generic processor

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor resolver to be a single function

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for debian

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for arch

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for alpine

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* allow for generic pkg and rel assertions in testpkg helper

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* do not allow for empty results

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* move stable deduplicate comment

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove relationship resolver type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2024-05-14 13:27:36 +00:00

96 lines
2.9 KiB
Go

package dependency
import (
"sort"
"github.com/scylladb/go-set/strset"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// Specification holds strings that indicate abstract resources that a package provides for other packages and
// requires for itself. These strings can represent anything from file paths, package names, or any other concept
// that is useful for dependency resolution within that packing ecosystem.
type Specification struct {
// Provides holds a list of abstract resources that this package provides for other packages.
Provides []string
// Requires holds a list of abstract resources that this package requires from other packages.
Requires []string
}
// Specifier is a function that takes a package and extracts a Specification, describing resources
// the package provides and needs.
type Specifier func(pkg.Package) Specification
// Processor returns a generic processor that will resolve relationships between packages based on the dependency claims.
func Processor(s Specifier) generic.Processor {
return func(pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
// we can't move forward unless all package IDs have been set
for idx, p := range pkgs {
id := p.ID()
if id == "" {
p.SetID()
pkgs[idx] = p
}
}
rels = append(rels, resolve(s, pkgs)...)
return pkgs, rels, err
}
}
// resolve will create relationships between packages based on the dependency claims of each package.
func resolve(specifier Specifier, pkgs []pkg.Package) (relationships []artifact.Relationship) {
pkgsProvidingResource := make(map[string][]artifact.ID)
pkgsByID := make(map[artifact.ID]pkg.Package)
specsByPkg := make(map[artifact.ID]Specification)
for _, p := range pkgs {
id := p.ID()
pkgsByID[id] = p
specsByPkg[id] = specifier(p)
for _, resource := range deduplicate(specifier(p).Provides) {
pkgsProvidingResource[resource] = append(pkgsProvidingResource[resource], id)
}
}
seen := strset.New()
for _, dependantPkg := range pkgs {
spec := specsByPkg[dependantPkg.ID()]
for _, resource := range deduplicate(spec.Requires) {
for _, providingPkgID := range pkgsProvidingResource[resource] {
// prevent creating duplicate relationships
pairKey := string(providingPkgID) + "-" + string(dependantPkg.ID())
if seen.Has(pairKey) {
continue
}
providingPkg := pkgsByID[providingPkgID]
relationships = append(relationships,
artifact.Relationship{
From: providingPkg,
To: dependantPkg,
Type: artifact.DependencyOfRelationship,
},
)
seen.Add(pairKey)
}
}
}
return relationships
}
func deduplicate(ss []string) []string {
// note: we sort the set such that multiple invocations of this function will be deterministic
set := strset.New(ss...)
list := set.List()
sort.Strings(list)
return list
}