mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 15:37:50 -08:00
refactor: unify Library and Package structs (#6633)
Signed-off-by: knqyf263 <knqyf263@gmail.com> Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency/types"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
@@ -56,13 +55,13 @@ type Parser struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewParser() types.Parser {
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
logger: log.WithPrefix("npm"),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) {
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile LockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
@@ -72,20 +71,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency,
|
||||
return nil, nil, xerrors.Errorf("decode error: %w", err)
|
||||
}
|
||||
|
||||
var libs []types.Library
|
||||
var deps []types.Dependency
|
||||
var pkgs []ftypes.Package
|
||||
var deps []ftypes.Dependency
|
||||
if lockFile.LockfileVersion == 1 {
|
||||
libs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string))
|
||||
pkgs, deps = p.parseV1(lockFile.Dependencies, make(map[string]string))
|
||||
} else {
|
||||
libs, deps = p.parseV2(lockFile.Packages)
|
||||
pkgs, deps = p.parseV2(lockFile.Packages)
|
||||
}
|
||||
|
||||
return utils.UniqueLibraries(libs), uniqueDeps(deps), nil
|
||||
return utils.UniquePackages(pkgs), uniqueDeps(deps), nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.Dependency) {
|
||||
libs := make(map[string]types.Library, len(packages)-1)
|
||||
var deps []types.Dependency
|
||||
func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftypes.Dependency) {
|
||||
pkgs := make(map[string]ftypes.Package, len(packages)-1)
|
||||
var deps []ftypes.Dependency
|
||||
|
||||
// Resolve links first
|
||||
// https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages
|
||||
@@ -116,51 +115,51 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.
|
||||
}
|
||||
|
||||
pkgID := packageID(pkgName, pkg.Version)
|
||||
location := types.Location{
|
||||
location := ftypes.Location{
|
||||
StartLine: pkg.StartLine,
|
||||
EndLine: pkg.EndLine,
|
||||
}
|
||||
|
||||
var ref types.ExternalRef
|
||||
var ref ftypes.ExternalRef
|
||||
if pkg.Resolved != "" {
|
||||
ref = types.ExternalRef{
|
||||
Type: types.RefOther,
|
||||
ref = ftypes.ExternalRef{
|
||||
Type: ftypes.RefOther,
|
||||
URL: pkg.Resolved,
|
||||
}
|
||||
}
|
||||
|
||||
pkgIndirect := isIndirectLib(pkgPath, directDeps)
|
||||
pkgIndirect := isIndirectPkg(pkgPath, directDeps)
|
||||
|
||||
// There are cases when similar libraries use same dependencies
|
||||
// There are cases when similar packages use same dependencies
|
||||
// we need to add location for each these dependencies
|
||||
if savedLib, ok := libs[pkgID]; ok {
|
||||
savedLib.Dev = savedLib.Dev && pkg.Dev
|
||||
if savedLib.Relationship == types.RelationshipIndirect && !pkgIndirect {
|
||||
savedLib.Relationship = types.RelationshipDirect
|
||||
if savedPkg, ok := pkgs[pkgID]; ok {
|
||||
savedPkg.Dev = savedPkg.Dev && pkg.Dev
|
||||
if savedPkg.Relationship == ftypes.RelationshipIndirect && !pkgIndirect {
|
||||
savedPkg.Relationship = ftypes.RelationshipDirect
|
||||
}
|
||||
|
||||
if ref.URL != "" && !slices.Contains(savedLib.ExternalReferences, ref) {
|
||||
savedLib.ExternalReferences = append(savedLib.ExternalReferences, ref)
|
||||
sortExternalReferences(savedLib.ExternalReferences)
|
||||
if ref.URL != "" && !slices.Contains(savedPkg.ExternalReferences, ref) {
|
||||
savedPkg.ExternalReferences = append(savedPkg.ExternalReferences, ref)
|
||||
sortExternalReferences(savedPkg.ExternalReferences)
|
||||
}
|
||||
|
||||
savedLib.Locations = append(savedLib.Locations, location)
|
||||
sort.Sort(savedLib.Locations)
|
||||
savedPkg.Locations = append(savedPkg.Locations, location)
|
||||
sort.Sort(savedPkg.Locations)
|
||||
|
||||
libs[pkgID] = savedLib
|
||||
pkgs[pkgID] = savedPkg
|
||||
continue
|
||||
}
|
||||
|
||||
lib := types.Library{
|
||||
newPkg := ftypes.Package{
|
||||
ID: pkgID,
|
||||
Name: pkgName,
|
||||
Version: pkg.Version,
|
||||
Relationship: lo.Ternary(pkgIndirect, types.RelationshipIndirect, types.RelationshipDirect),
|
||||
Relationship: lo.Ternary(pkgIndirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect),
|
||||
Dev: pkg.Dev,
|
||||
ExternalReferences: lo.Ternary(ref.URL != "", []types.ExternalRef{ref}, nil),
|
||||
Locations: []types.Location{location},
|
||||
ExternalReferences: lo.Ternary(ref.URL != "", []ftypes.ExternalRef{ref}, nil),
|
||||
Locations: []ftypes.Location{location},
|
||||
}
|
||||
libs[pkgID] = lib
|
||||
pkgs[pkgID] = newPkg
|
||||
|
||||
// npm builds graph using optional deps. e.g.:
|
||||
// └─┬ watchpack@1.7.5
|
||||
@@ -179,15 +178,15 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types.
|
||||
}
|
||||
|
||||
if len(dependsOn) > 0 {
|
||||
deps = append(deps, types.Dependency{
|
||||
ID: lib.ID,
|
||||
deps = append(deps, ftypes.Dependency{
|
||||
ID: newPkg.ID,
|
||||
DependsOn: dependsOn,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return maps.Values(libs), deps
|
||||
return maps.Values(pkgs), deps
|
||||
}
|
||||
|
||||
// for local package npm uses links. e.g.:
|
||||
@@ -271,73 +270,73 @@ func findDependsOn(pkgPath, depName string, packages map[string]Package) (string
|
||||
return "", xerrors.Errorf("can't find dependsOn for %s", depName)
|
||||
}
|
||||
|
||||
func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]types.Library, []types.Dependency) {
|
||||
func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string]string) ([]ftypes.Package, []ftypes.Dependency) {
|
||||
// Update package name and version mapping.
|
||||
for pkgName, dep := range dependencies {
|
||||
// Overwrite the existing package version so that the nested version can take precedence.
|
||||
versions[pkgName] = dep.Version
|
||||
}
|
||||
|
||||
var libs []types.Library
|
||||
var deps []types.Dependency
|
||||
var pkgs []ftypes.Package
|
||||
var deps []ftypes.Dependency
|
||||
for pkgName, dep := range dependencies {
|
||||
lib := types.Library{
|
||||
pkg := ftypes.Package{
|
||||
ID: packageID(pkgName, dep.Version),
|
||||
Name: pkgName,
|
||||
Version: dep.Version,
|
||||
Dev: dep.Dev,
|
||||
Relationship: types.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies
|
||||
ExternalReferences: []types.ExternalRef{
|
||||
Relationship: ftypes.RelationshipUnknown, // lockfile v1 schema doesn't have information about direct dependencies
|
||||
ExternalReferences: []ftypes.ExternalRef{
|
||||
{
|
||||
Type: types.RefOther,
|
||||
Type: ftypes.RefOther,
|
||||
URL: dep.Resolved,
|
||||
},
|
||||
},
|
||||
Locations: []types.Location{
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: dep.StartLine,
|
||||
EndLine: dep.EndLine,
|
||||
},
|
||||
},
|
||||
}
|
||||
libs = append(libs, lib)
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
dependsOn := make([]string, 0, len(dep.Requires))
|
||||
for libName, requiredVer := range dep.Requires {
|
||||
for pName, requiredVer := range dep.Requires {
|
||||
// Try to resolve the version with nested dependencies first
|
||||
if resolvedDep, ok := dep.Dependencies[libName]; ok {
|
||||
libID := packageID(libName, resolvedDep.Version)
|
||||
dependsOn = append(dependsOn, libID)
|
||||
if resolvedDep, ok := dep.Dependencies[pName]; ok {
|
||||
pkgID := packageID(pName, resolvedDep.Version)
|
||||
dependsOn = append(dependsOn, pkgID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to resolve the version with the higher level dependencies
|
||||
if ver, ok := versions[libName]; ok {
|
||||
dependsOn = append(dependsOn, packageID(libName, ver))
|
||||
if ver, ok := versions[pName]; ok {
|
||||
dependsOn = append(dependsOn, packageID(pName, ver))
|
||||
continue
|
||||
}
|
||||
|
||||
// It should not reach here.
|
||||
p.logger.Warn("Unable to resolve the version",
|
||||
log.String("name", libName), log.String("version", requiredVer))
|
||||
log.String("name", pName), log.String("version", requiredVer))
|
||||
}
|
||||
|
||||
if len(dependsOn) > 0 {
|
||||
deps = append(deps, types.Dependency{
|
||||
ID: packageID(lib.Name, lib.Version),
|
||||
deps = append(deps, ftypes.Dependency{
|
||||
ID: packageID(pkg.Name, pkg.Version),
|
||||
DependsOn: dependsOn,
|
||||
})
|
||||
}
|
||||
|
||||
if dep.Dependencies != nil {
|
||||
// Recursion
|
||||
childLibs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions))
|
||||
libs = append(libs, childLibs...)
|
||||
childpkgs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions))
|
||||
pkgs = append(pkgs, childpkgs...)
|
||||
deps = append(deps, childDeps...)
|
||||
}
|
||||
}
|
||||
|
||||
return libs, deps
|
||||
return pkgs, deps
|
||||
}
|
||||
|
||||
func (p *Parser) pkgNameFromPath(pkgPath string) string {
|
||||
@@ -354,8 +353,8 @@ func (p *Parser) pkgNameFromPath(pkgPath string) string {
|
||||
return pkgPath
|
||||
}
|
||||
|
||||
func uniqueDeps(deps []types.Dependency) []types.Dependency {
|
||||
var uniqDeps []types.Dependency
|
||||
func uniqueDeps(deps []ftypes.Dependency) []ftypes.Dependency {
|
||||
var uniqDeps ftypes.Dependencies
|
||||
unique := make(map[string]struct{})
|
||||
|
||||
for _, dep := range deps {
|
||||
@@ -367,14 +366,14 @@ func uniqueDeps(deps []types.Dependency) []types.Dependency {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(types.Dependencies(uniqDeps))
|
||||
sort.Sort(uniqDeps)
|
||||
return uniqDeps
|
||||
}
|
||||
|
||||
func isIndirectLib(pkgPath string, directDeps map[string]struct{}) bool {
|
||||
func isIndirectPkg(pkgPath string, directDeps map[string]struct{}) bool {
|
||||
// A project can contain 2 different versions of the same dependency.
|
||||
// e.g. `node_modules/string-width/node_modules/strip-ansi` and `node_modules/string-ansi`
|
||||
// direct dependencies always have root path (`node_modules/<lib_name>`)
|
||||
// direct dependencies always have root path (`node_modules/<pkg_name>`)
|
||||
if _, ok := directDeps[pkgPath]; ok {
|
||||
return false
|
||||
}
|
||||
@@ -411,7 +410,7 @@ func packageID(name, version string) string {
|
||||
return dependency.ID(ftypes.Npm, name, version)
|
||||
}
|
||||
|
||||
func sortExternalReferences(refs []types.ExternalRef) {
|
||||
func sortExternalReferences(refs []ftypes.ExternalRef) {
|
||||
sort.Slice(refs, func(i, j int) bool {
|
||||
if refs[i].Type != refs[j].Type {
|
||||
return refs[i].Type < refs[j].Type
|
||||
|
||||
Reference in New Issue
Block a user