fix: duplicate packages, support pnpm lockfile v6 (#1778)

This commit is contained in:
Keith Zantow 2023-05-23 10:24:25 -04:00 committed by GitHub
parent 798af57853
commit a3c5550217
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 277 additions and 5 deletions

View file

@ -3,10 +3,13 @@ package javascript
import (
"fmt"
"io"
"regexp"
"strconv"
"strings"
"gopkg.in/yaml.v3"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
@ -17,8 +20,9 @@ import (
var _ generic.Parser = parsePnpmLock
type pnpmLockYaml struct {
Dependencies map[string]string `json:"dependencies"`
Packages map[string]interface{} `json:"packages"`
Version string `json:"lockfileVersion" yaml:"lockfileVersion"`
Dependencies map[string]interface{} `json:"dependencies" yaml:"dependencies"`
Packages map[string]interface{} `json:"packages" yaml:"packages"`
}
func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
@ -34,19 +38,55 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader
return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err)
}
for name, version := range lockFile.Dependencies {
lockVersion, _ := strconv.ParseFloat(lockFile.Version, 64)
for name, info := range lockFile.Dependencies {
version := ""
switch info := info.(type) {
case string:
version = info
case map[string]interface{}:
v, ok := info["version"]
if !ok {
break
}
ver, ok := v.(string)
if ok {
version = parseVersion(ver)
}
default:
log.Tracef("unsupported pnpm dependency type: %+v", info)
continue
}
if hasPkg(pkgs, name, version) {
continue
}
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
}
packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`)
splitChar := "/"
if lockVersion >= 6.0 {
splitChar = "@"
}
// parse packages from packages section of pnpm-lock.yaml
for nameVersion := range lockFile.Packages {
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), "/")
nameVersion = packageNameRegex.ReplaceAllString(nameVersion, "$1")
nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), splitChar)
// last element in split array is version
version := nameVersionSplit[len(nameVersionSplit)-1]
// construct name from all array items other than last item (version)
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], "/")
name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], splitChar)
if hasPkg(pkgs, name, version) {
continue
}
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
}
@ -55,3 +95,16 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader
return pkgs, nil, nil
}
func hasPkg(pkgs []pkg.Package, name, version string) bool {
for _, p := range pkgs {
if p.Name == name && p.Version == version {
return true
}
}
return false
}
func parseVersion(version string) string {
return strings.SplitN(version, "(", 2)[0]
}

View file

@ -52,3 +52,95 @@ func TestParsePnpmLock(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
}
func TestParsePnpmV6Lock(t *testing.T) {
var expectedRelationships []artifact.Relationship
fixture := "test-fixtures/pnpm-v6/pnpm-lock.yaml"
locationSet := source.NewLocationSet(source.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
Name: "@testing-library/jest-dom",
Version: "5.16.5",
PURL: "pkg:npm/%40testing-library/jest-dom@5.16.5",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@testing-library/react",
Version: "13.4.0",
PURL: "pkg:npm/%40testing-library/react@13.4.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@testing-library/user-event",
Version: "13.5.0",
PURL: "pkg:npm/%40testing-library/user-event@13.5.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "react",
Version: "18.2.0",
PURL: "pkg:npm/react@18.2.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "react-dom",
Version: "18.2.0",
PURL: "pkg:npm/react-dom@18.2.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "web-vitals",
Version: "2.1.4",
PURL: "pkg:npm/web-vitals@2.1.4",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@babel/core",
Version: "7.21.4",
PURL: "pkg:npm/%40babel/core@7.21.4",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@types/eslint",
Version: "8.37.0",
PURL: "pkg:npm/%40types/eslint@8.37.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "read-cache",
Version: "1.0.0",
PURL: "pkg:npm/read-cache@1.0.0",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "schema-utils",
Version: "3.1.2",
PURL: "pkg:npm/schema-utils@3.1.2",
Locations: locationSet,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
}
pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships)
}

View file

@ -0,0 +1,127 @@
lockfileVersion: '6.0'
dependencies:
'@testing-library/jest-dom':
specifier: ^5.16.5
version: 5.16.5
'@testing-library/react':
specifier: ^13.4.0
version: 13.4.0(react-dom@18.2.0)(react@18.2.0)
'@testing-library/user-event':
specifier: ^13.5.0
version: 13.5.0(@testing-library/dom@9.2.0)
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
web-vitals:
specifier: ^2.1.4
version: 2.1.4
packages:
/@babel/core@7.21.4:
resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.2.1
'@babel/code-frame': 7.21.4
'@babel/generator': 7.21.4
'@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4)
'@babel/helper-module-transforms': 7.21.2
'@babel/helpers': 7.21.0
'@babel/parser': 7.21.4
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
convert-source-map: 1.9.0
debug: 4.3.4
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: false
/@testing-library/jest-dom@5.16.5:
resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==}
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
dependencies:
'@adobe/css-tools': 4.2.0
'@babel/runtime': 7.21.0
'@types/testing-library__jest-dom': 5.14.5
aria-query: 5.1.3
chalk: 3.0.0
css.escape: 1.5.1
dom-accessibility-api: 0.5.16
lodash: 4.17.21
redent: 3.0.0
dev: false
/@testing-library/react@13.4.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==}
engines: {node: '>=12'}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
dependencies:
'@babel/runtime': 7.21.0
'@testing-library/dom': 8.20.0
'@types/react-dom': 18.2.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@testing-library/user-event@13.5.0(@testing-library/dom@9.2.0):
resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==}
engines: {node: '>=10', npm: '>=6'}
peerDependencies:
'@testing-library/dom': '>=7.21.4'
dependencies:
'@babel/runtime': 7.21.0
'@testing-library/dom': 9.2.0
dev: false
/@types/eslint@8.37.0:
resolution: {integrity: sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==}
dependencies:
'@types/estree': 1.0.1
'@types/json-schema': 7.0.11
dev: false
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
react: ^18.2.0
dependencies:
loose-envify: 1.4.0
react: 18.2.0
scheduler: 0.23.0
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
dev: false
/read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
dependencies:
pify: 2.3.0
dev: false
/schema-utils@3.1.2:
resolution: {integrity: sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/json-schema': 7.0.11
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: false
/web-vitals@2.1.4:
resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==}
dev: false