Compare commits

...
7 Commits
26 changed files with 352 additions and 45 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ GOLANGCILINT_VERSION = v1.50.1
GOSIMPORTS_VERSION = v0.3.4
BOUNCER_VERSION = v0.4.0
CHRONICLE_VERSION = v0.4.2
GORELEASER_VERSION = v1.12.3
GORELEASER_VERSION = v1.13.0
YAJSV_VERSION = v1.4.1
COSIGN_VERSION = v1.13.1
QUILL_VERSION = v0.2.0
@@ -105,7 +105,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
},
Packages: toPackages(s.Artifacts.PackageCatalog),
Files: toFiles(s),
Relationships: toRelationships(s.Relationships),
Relationships: toRelationships(s.RelationshipsSorted()),
}
}
+25
View File
@@ -2,8 +2,10 @@ package testutils
import (
"bytes"
"math/rand"
"strings"
"testing"
"time"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/stretchr/testify/assert"
@@ -12,6 +14,7 @@ import (
"github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
@@ -276,3 +279,25 @@ func newDirectoryCatalog() *pkg.Catalog {
return catalog
}
//nolint:gosec
func AddSampleFileRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted()
s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{}
files := []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"}
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
rnd.Shuffle(len(files), func(i, j int) { files[i], files[j] = files[j], files[i] })
for _, f := range files {
meta := source.FileMetadata{}
coords := source.Coordinates{RealPath: f}
s.Artifacts.FileMetadata[coords] = meta
s.Relationships = append(s.Relationships, artifact.Relationship{
From: catalog[0],
To: coords,
Type: artifact.ContainsRelationship,
})
}
}
+5
View File
@@ -2,10 +2,12 @@ package formats
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/formats/cyclonedxjson"
"github.com/anchore/syft/syft/formats/cyclonedxxml"
"github.com/anchore/syft/syft/formats/github"
@@ -35,6 +37,9 @@ func Formats() []sbom.Format {
func Identify(by []byte) sbom.Format {
for _, f := range Formats() {
if err := f.Validate(bytes.NewReader(by)); err != nil {
if !errors.Is(err, sbom.ErrValidationNotSupported) {
log.Debugf("format %s returned err: %+v", f.ID(), err)
}
continue
}
return f
+1 -21
View File
@@ -5,10 +5,7 @@ import (
"regexp"
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/formats/common/testutils"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
var updateSpdxJson = flag.Bool("update-spdx-json", false, "update the *.golden files for spdx-json encoders")
@@ -36,7 +33,7 @@ func TestSPDXJSONImageEncoder(t *testing.T) {
func TestSPDXRelationshipOrder(t *testing.T) {
testImage := "image-simple"
s := testutils.ImageInput(t, testImage, testutils.FromSnapshot())
addRelationships(&s)
testutils.AddSampleFileRelationships(&s)
testutils.AssertEncoderAgainstGoldenImageSnapshot(t,
Format(),
s,
@@ -46,23 +43,6 @@ func TestSPDXRelationshipOrder(t *testing.T) {
)
}
func addRelationships(s *sbom.SBOM) {
catalog := s.Artifacts.PackageCatalog.Sorted()
s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{}
for _, f := range []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"} {
meta := source.FileMetadata{}
coords := source.Coordinates{RealPath: f}
s.Artifacts.FileMetadata[coords] = meta
s.Relationships = append(s.Relationships, artifact.Relationship{
From: catalog[0],
To: coords,
Type: artifact.ContainsRelationship,
})
}
}
func spdxJsonRedactor(s []byte) []byte {
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`"created": .*`).ReplaceAll(s, []byte("redacted"))
@@ -3,14 +3,14 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "/some/path",
"documentNamespace": "https://anchore.com/syft/dir/some/path-0f9b165e-1819-43cb-bd58-f61c1c23d6cf",
"documentNamespace": "https://anchore.com/syft/dir/some/path-4bf54cdd-0a0f-4560-bf4f-39cac2ef7a5b",
"creationInfo": {
"licenseListVersion": "3.18",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"created": "2022-11-11T19:24:55Z",
"created": "2022-11-19T13:46:57Z",
"comment": ""
},
"packages": [
@@ -3,14 +3,14 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "user-image-input",
"documentNamespace": "https://anchore.com/syft/image/user-image-input-5841d063-c3ef-406b-91b4-8a702ef45ce9",
"documentNamespace": "https://anchore.com/syft/image/user-image-input-102ca7dc-3d1e-46d2-b130-28968831ebcc",
"creationInfo": {
"licenseListVersion": "3.18",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"created": "2022-11-11T19:24:55Z",
"created": "2022-11-19T13:46:57Z",
"comment": ""
},
"packages": [
@@ -3,14 +3,14 @@
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "user-image-input",
"documentNamespace": "https://anchore.com/syft/image/user-image-input-8755f340-f205-4bf2-a909-94c623670734",
"documentNamespace": "https://anchore.com/syft/image/user-image-input-55ad4afc-ecdc-46a4-8bc3-36b3e72da5d1",
"creationInfo": {
"licenseListVersion": "3.18",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-v0.42.0-bogus"
],
"created": "2022-11-11T19:24:55Z",
"created": "2022-11-19T13:46:57Z",
"comment": ""
},
"packages": [
@@ -133,12 +133,12 @@
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd",
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f",
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6",
"relationshipType": "CONTAINS"
},
{
@@ -148,12 +148,12 @@
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
"relatedSpdxElement": "SPDXRef-839d99ee67d9d174",
"relatedSpdxElement": "SPDXRef-c6f5b29dca12661f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
"relatedSpdxElement": "SPDXRef-9c2f7510199b17f6",
"relatedSpdxElement": "SPDXRef-f9e49132a4b96ccd",
"relationshipType": "CONTAINS"
}
]
+1 -1
View File
@@ -13,7 +13,7 @@ import (
func decoder(reader io.Reader) (*sbom.SBOM, error) {
doc, err := tvloader.Load2_3(reader)
if err != nil {
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
return nil, fmt.Errorf("unable to decode spdx-tag-value: %w", err)
}
return spdxhelpers.ToSyftModel(doc)
+13
View File
@@ -67,6 +67,19 @@ func TestSPDXJSONSPDXIDs(t *testing.T) {
)
}
func TestSPDXRelationshipOrder(t *testing.T) {
testImage := "image-simple"
s := testutils.ImageInput(t, testImage, testutils.FromSnapshot())
testutils.AddSampleFileRelationships(&s)
testutils.AssertEncoderAgainstGoldenImageSnapshot(t,
Format(),
s,
testImage,
*updateSpdxTagValue,
spdxTagValueRedactor,
)
}
func spdxTagValueRedactor(s []byte) []byte {
// each SBOM reports the time it was generated, which is not useful during snapshot testing
s = regexp.MustCompile(`Created: .*`).ReplaceAll(s, []byte("redacted"))
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: foobar/baz
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-3d730196-4510-4ee4-9743-9322dd27cee7
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-62bc0aae-2b37-4c86-ab79-63c6fc4198ed
LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus
Created: 2022-11-18T14:21:45Z
Created: 2022-11-19T13:48:30Z
##### Package: @at-sign
@@ -0,0 +1,79 @@
SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: user-image-input
DocumentNamespace: https://anchore.com/syft/image/user-image-input-cc20e416-9c74-401c-b4aa-245556bada5e
LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus
Created: 2022-11-19T13:48:30Z
##### Unpackaged files
FileName: /f1
SPDXID: SPDXRef-5265a4dde3edbf7c
FileType: OTHER
LicenseConcluded: NOASSERTION
FileName: /z1/f5
SPDXID: SPDXRef-839d99ee67d9d174
FileType: OTHER
LicenseConcluded: NOASSERTION
FileName: /a1/f6
SPDXID: SPDXRef-9c2f7510199b17f6
FileType: OTHER
LicenseConcluded: NOASSERTION
FileName: /d2/f4
SPDXID: SPDXRef-c641caa71518099f
FileType: OTHER
LicenseConcluded: NOASSERTION
FileName: /d1/f3
SPDXID: SPDXRef-c6f5b29dca12661f
FileType: OTHER
LicenseConcluded: NOASSERTION
FileName: /f2
SPDXID: SPDXRef-f9e49132a4b96ccd
FileType: OTHER
LicenseConcluded: NOASSERTION
##### Package: package-2
PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
PackageVersion: 2.0.1
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
PackageLicenseConcluded: NONE
PackageLicenseDeclared: NONE
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
PackageVersion: 1.0.1
PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
PackageLicenseConcluded: MIT
PackageLicenseDeclared: MIT
PackageCopyrightText: NOASSERTION
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-f9e49132a4b96ccd
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: /some/path
DocumentNamespace: https://anchore.com/syft/dir/some/path-b6078c95-5b97-462d-acb3-9e74bc9ddb43
DocumentNamespace: https://anchore.com/syft/dir/some/path-7a4b2140-6669-4a28-80dd-5c8e795c5da0
LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus
Created: 2022-11-18T14:21:44Z
Created: 2022-11-19T13:48:30Z
##### Package: package-2
@@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: user-image-input
DocumentNamespace: https://anchore.com/syft/image/user-image-input-aa272d1e-8bb4-411f-a554-4c9a16ea66fb
DocumentNamespace: https://anchore.com/syft/image/user-image-input-baff7ada-85cb-403e-90d7-05b0c6d79490
LicenseListVersion: 3.18
Creator: Organization: Anchore, Inc
Creator: Tool: syft-v0.42.0-bogus
Created: 2022-11-18T14:21:45Z
Created: 2022-11-19T13:48:30Z
##### Package: package-2
+6
View File
@@ -42,10 +42,12 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
if err != nil {
return nil, nil, err
}
pkgFiles, err := parseMtree(r)
if err != nil {
return nil, nil, err
}
// The replace the files found the the pacman database with the files from the mtree These contain more metadata and
// thus more useful.
metadata.Files = pkgFiles
@@ -106,6 +108,10 @@ func getFileReader(path string, resolver source.FileResolver) (io.Reader, error)
if err != nil {
return nil, err
}
if len(locs) == 0 {
return nil, fmt.Errorf("could not find file: %s", path)
}
// TODO: Should we maybe check if we found the file
dbContentReader, err := resolver.FileContentsByLocation(locs[0])
if err != nil {
@@ -34,6 +34,14 @@ func parseCabalFreeze(_ source.FileResolver, _ *generic.Environment, reader sour
line = strings.TrimSpace(line)
startPkgEncoding, endPkgEncoding := strings.Index(line, "any.")+4, strings.Index(line, ",")
// case where comma not found for last package in constraint list
if endPkgEncoding == -1 {
endPkgEncoding = len(line)
}
if startPkgEncoding >= endPkgEncoding || startPkgEncoding < 0 {
continue
}
line = line[startPkgEncoding:endPkgEncoding]
fields := strings.Split(line, " ==")
@@ -12,6 +12,6 @@ constraints: any.Cabal ==3.2.1.0,
any.RSA ==2.4.1,
any.SHA ==1.6.4.4,
void -safe,
any.Spock ==0.14.0.0,
any.Spock ==0.14.0.0
index-state: hackage.haskell.org 2022-07-07T01:01:53Z
+17 -2
View File
@@ -44,14 +44,29 @@ func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Pack
}
func newPackageLockV1Package(resolver source.FileResolver, location source.Location, name string, u lockDependency) pkg.Package {
version := u.Version
const aliasPrefixPackageLockV1 = "npm:"
// Handles type aliases https://github.com/npm/rfcs/blob/main/implemented/0001-package-aliases.md
if strings.HasPrefix(version, aliasPrefixPackageLockV1) {
// this is an alias.
// `"version": "npm:canonical-name@X.Y.Z"`
canonicalPackageAndVersion := version[len(aliasPrefixPackageLockV1):]
versionSeparator := strings.LastIndex(canonicalPackageAndVersion, "@")
name = canonicalPackageAndVersion[:versionSeparator]
version = canonicalPackageAndVersion[versionSeparator+1:]
}
return finalizeLockPkg(
resolver,
location,
pkg.Package{
Name: name,
Version: u.Version,
Version: version,
Locations: source.NewLocationSet(location),
PURL: packageURL(name, u.Version),
PURL: packageURL(name, version),
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
@@ -75,6 +75,11 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read
}
}
// handles alias names
if pkgMeta.Name != "" {
name = pkgMeta.Name
}
pkgs = append(pkgs, newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta))
}
}
@@ -193,3 +193,57 @@ func TestParsePackageLockV3(t *testing.T) {
}
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships)
}
func TestParsePackageLockAlias(t *testing.T) {
var expectedRelationships []artifact.Relationship
commonPkgs := []pkg.Package{
{
Name: "case",
Version: "1.6.2",
PURL: "pkg:npm/case@1.6.2",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "case",
Version: "1.6.3",
PURL: "pkg:npm/case@1.6.3",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
{
Name: "@bundled-es-modules/chai",
Version: "4.2.2",
PURL: "pkg:npm/%40bundled-es-modules/chai@4.2.2",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
}
v2Pkg := pkg.Package{
Name: "alias-check",
Version: "1.0.0",
PURL: "pkg:npm/alias-check@1.0.0",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: []string{"ISC"},
}
packageLockV1 := "test-fixtures/pkg-lock/alias-package-lock-1.json"
packageLockV2 := "test-fixtures/pkg-lock/alias-package-lock-2.json"
packageLocks := []string{packageLockV1, packageLockV2}
for _, packageLock := range packageLocks {
expected := make([]pkg.Package, len(commonPkgs))
copy(expected, commonPkgs)
if packageLock == packageLockV2 {
expected = append(expected, v2Pkg)
}
for i := range expected {
expected[i].Locations.Add(source.NewLocation(packageLock))
}
pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships)
}
}
@@ -0,0 +1,23 @@
{
"name": "alias-check",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"case": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz",
"integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg=="
},
"case-alias": {
"version": "npm:case@1.6.3",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz",
"integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ=="
},
"chai": {
"version": "npm:@bundled-es-modules/chai@4.2.2",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz",
"integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg=="
}
}
}
@@ -0,0 +1,58 @@
{
"name": "alias-check",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "alias-check",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"case": "1.6.2",
"case-alias": "npm:case@^1.6.3",
"chai": "npm:@bundled-es-modules/chai@^4.2.2"
}
},
"node_modules/case": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz",
"integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/case-alias": {
"name": "case",
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz",
"integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/chai": {
"name": "@bundled-es-modules/chai",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz",
"integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg=="
}
},
"dependencies": {
"case": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.2.tgz",
"integrity": "sha512-ll380ZRoraT7mUK2G92UbH+FJVD5AwdVIAYk9xhV1tauh0carDgYByUD1HhjCWsWgxrfQvCeHvtfj7IYR6TKeg=="
},
"case-alias": {
"version": "npm:case@1.6.3",
"resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz",
"integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ=="
},
"chai": {
"version": "npm:@bundled-es-modules/chai@4.2.2",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/chai/-/chai-4.2.2.tgz",
"integrity": "sha512-iGmVYw2/zJCoqyKTtWEYCtFmMyi8WmACQKtky0lpNyEKWX0YIOpKWGD7saMXL+tPpllss0otilxV0SLwyi3Ytg=="
}
}
}
@@ -4,6 +4,7 @@ import (
"bufio"
"fmt"
"strings"
"unicode"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
@@ -47,8 +48,14 @@ func parseRequirementsTxt(_ source.FileResolver, _ *generic.Environment, reader
log.WithFields("path", reader.RealPath).Warnf("unable to parse requirements.txt line: %q", line)
continue
}
// check if the version contains hash declarations on the same line
version, _ := parseVersionAndHashes(parts[1])
name := strings.TrimSpace(parts[0])
version := strings.TrimSpace(parts[1])
version = strings.TrimFunc(version, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})
packages = append(packages, newPackageForIndex(name, version, reader.Location))
}
@@ -59,6 +66,15 @@ func parseRequirementsTxt(_ source.FileResolver, _ *generic.Environment, reader
return packages, nil, nil
}
func parseVersionAndHashes(version string) (string, []string) {
parts := strings.Split(version, "--hash=")
if len(parts) < 2 {
return version, nil
}
return parts[0], parts[1:]
}
// trimRequirementsTxtLine removes content from the given requirements.txt line
// that should not be considered for parsing.
func trimRequirementsTxtLine(line string) string {
@@ -37,6 +37,22 @@ func TestParseRequirementsTxt(t *testing.T) {
Language: pkg.Python,
Type: pkg.PythonPkg,
},
{
Name: "argh",
Version: "0.26.2",
PURL: "pkg:pypi/argh@0.26.2",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
},
{
Name: "argh",
Version: "0.26.3",
PURL: "pkg:pypi/argh@0.26.3",
Locations: locations,
Language: pkg.Python,
Type: pkg.PythonPkg,
},
}
var expectedRelationships []artifact.Relationship
@@ -10,3 +10,7 @@ coverage != 3.5 # Version Exclusion. Anything except version 3.5
numpyNew; sys_platform == 'win32'
numpy >= 3.4.1; sys_platform == 'win32'
Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
argh==0.26.2 \
--hash=sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3 \
--hash=sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65
argh==0.26.3 --hash=sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3 --hash=sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65