encode upstream qualifier on os package pURLs (#769)

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2022-01-25 09:55:56 -05:00 committed by GitHub
parent d2e815a2c5
commit 6f0fad7ffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 26 deletions

View File

@ -49,6 +49,14 @@ type ApkFileRecord struct {
// PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec) // PackageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec)
func (m ApkMetadata) PackageURL(distro *linux.Release) string { func (m ApkMetadata) PackageURL(distro *linux.Release) string {
qualifiers := map[string]string{
purlArchQualifier: m.Architecture,
}
if m.OriginPackage != "" {
qualifiers[purlUpstreamQualifier] = m.OriginPackage
}
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
// note: this is currently a candidate and not technically within spec // note: this is currently a candidate and not technically within spec
// see https://github.com/package-url/purl-spec#other-candidate-types-to-define // see https://github.com/package-url/purl-spec#other-candidate-types-to-define
@ -57,9 +65,7 @@ func (m ApkMetadata) PackageURL(distro *linux.Release) string {
m.Package, m.Package,
m.Version, m.Version,
purlQualifiers( purlQualifiers(
map[string]string{ qualifiers,
purlArchQualifier: m.Architecture,
},
distro, distro,
), ),
"", "",

View File

@ -31,7 +31,7 @@ func TestApkMetadata_pURL(t *testing.T) {
expected: "pkg:alpine/p@v?arch=a&distro=alpine-3.4.6", expected: "pkg:alpine/p@v?arch=a&distro=alpine-3.4.6",
}, },
{ {
name: "missing architecure", name: "missing architecture",
metadata: ApkMetadata{ metadata: ApkMetadata{
Package: "p", Package: "p",
Version: "v", Version: "v",
@ -67,6 +67,20 @@ func TestApkMetadata_pURL(t *testing.T) {
}, },
expected: "pkg:alpine/g%20plus%20plus@v84?arch=am86&distro=alpine-3.15.0", expected: "pkg:alpine/g%20plus%20plus@v84?arch=am86&distro=alpine-3.15.0",
}, },
{
name: "add source information as qualifier",
metadata: ApkMetadata{
Package: "p",
Version: "v",
Architecture: "a",
OriginPackage: "origin",
},
distro: linux.Release{
ID: "alpine",
VersionID: "3.4.6",
},
expected: "pkg:alpine/p@v?arch=a&upstream=origin&distro=alpine-3.4.6",
},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -1,6 +1,7 @@
package pkg package pkg
import ( import (
"fmt"
"sort" "sort"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
@ -43,6 +44,19 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
if distro != nil { if distro != nil {
namespace = distro.ID namespace = distro.ID
} }
qualifiers := map[string]string{
purlArchQualifier: m.Architecture,
}
if m.Source != "" {
if m.SourceVersion != "" {
qualifiers[purlUpstreamQualifier] = fmt.Sprintf("%s@%s", m.Source, m.SourceVersion)
} else {
qualifiers[purlUpstreamQualifier] = m.Source
}
}
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
// TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21 // TODO: replace with `packageurl.TypeDebian` upon merge of https://github.com/package-url/packageurl-go/pull/21
// TODO: or, since we're now using an Anchore fork of this module, we could do this sooner. // TODO: or, since we're now using an Anchore fork of this module, we could do this sooner.
@ -51,9 +65,7 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
m.Package, m.Package,
m.Version, m.Version,
purlQualifiers( purlQualifiers(
map[string]string{ qualifiers,
purlArchQualifier: m.Architecture,
},
distro, distro,
), ),
"", "",

View File

@ -25,7 +25,6 @@ func TestDpkgMetadata_pURL(t *testing.T) {
}, },
metadata: DpkgMetadata{ metadata: DpkgMetadata{
Package: "p", Package: "p",
Source: "s",
Version: "v", Version: "v",
}, },
expected: "pkg:deb/debian/p@v?distro=debian-11", expected: "pkg:deb/debian/p@v?distro=debian-11",
@ -38,7 +37,6 @@ func TestDpkgMetadata_pURL(t *testing.T) {
}, },
metadata: DpkgMetadata{ metadata: DpkgMetadata{
Package: "p", Package: "p",
Source: "s",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
}, },
@ -48,11 +46,37 @@ func TestDpkgMetadata_pURL(t *testing.T) {
name: "missing distro", name: "missing distro",
metadata: DpkgMetadata{ metadata: DpkgMetadata{
Package: "p", Package: "p",
Source: "s",
Version: "v", Version: "v",
}, },
expected: "pkg:deb/p@v", expected: "pkg:deb/p@v",
}, },
{
name: "with upstream qualifier with source pkg name info",
distro: &linux.Release{
ID: "debian",
VersionID: "11",
},
metadata: DpkgMetadata{
Package: "p",
Source: "s",
Version: "v",
},
expected: "pkg:deb/debian/p@v?upstream=s&distro=debian-11",
},
{
name: "with upstream qualifier with source pkg name and version info",
distro: &linux.Release{
ID: "debian",
VersionID: "11",
},
metadata: DpkgMetadata{
Package: "p",
Source: "s",
Version: "v",
SourceVersion: "2.3",
},
expected: "pkg:deb/debian/p@v?upstream=s@2.3&distro=debian-11",
},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -106,7 +106,7 @@ func (p PythonDirectURLOriginInfo) vcsURLQualifier() packageurl.Qualifiers {
if p.VCS != "" { if p.VCS != "" {
// Taken from https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs // Taken from https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs
// packageurl-go still doesn't support all qualifier names // packageurl-go still doesn't support all qualifier names
return packageurl.Qualifiers{{Key: purlVCSURL, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}} return packageurl.Qualifiers{{Key: purlVCSURLQualifier, Value: fmt.Sprintf("%s+%s@%s", p.VCS, p.URL, p.CommitID)}}
} }
return nil return nil
} }

View File

@ -63,6 +63,10 @@ func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
qualifiers[purlEpochQualifier] = strconv.Itoa(*m.Epoch) qualifiers[purlEpochQualifier] = strconv.Itoa(*m.Epoch)
} }
if m.SourceRpm != "" {
qualifiers[purlUpstreamQualifier] = m.SourceRpm
}
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
packageurl.TypeRPM, packageurl.TypeRPM,
namespace, namespace,

View File

@ -17,6 +17,20 @@ func TestRpmMetadata_pURL(t *testing.T) {
metadata RpmdbMetadata metadata RpmdbMetadata
expected string expected string
}{ }{
{
name: "go case",
distro: &linux.Release{
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
Name: "p",
Version: "v",
Release: "r",
Epoch: nil,
},
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
},
{ {
name: "with arch and epoch", name: "with arch and epoch",
distro: &linux.Release{ distro: &linux.Release{
@ -32,20 +46,6 @@ func TestRpmMetadata_pURL(t *testing.T) {
}, },
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7", expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7",
}, },
{
name: "go case",
distro: &linux.Release{
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
Name: "p",
Version: "v",
Release: "r",
Epoch: nil,
},
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
},
{ {
name: "missing distro", name: "missing distro",
metadata: RpmdbMetadata{ metadata: RpmdbMetadata{
@ -56,6 +56,20 @@ func TestRpmMetadata_pURL(t *testing.T) {
}, },
expected: "pkg:rpm/p@v-r", expected: "pkg:rpm/p@v-r",
}, },
{
name: "with upstream source rpm info",
distro: &linux.Release{
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
Name: "p",
Version: "v",
Release: "r",
SourceRpm: "sourcerpm",
},
expected: "pkg:rpm/rhel/p@v-r?upstream=sourcerpm&distro=rhel-8.4",
},
} }
for _, test := range tests { for _, test := range tests {

View File

@ -14,7 +14,10 @@ const (
purlArchQualifier = "arch" purlArchQualifier = "arch"
purlDistroQualifier = "distro" purlDistroQualifier = "distro"
purlEpochQualifier = "epoch" purlEpochQualifier = "epoch"
purlVCSURL = "vcs_url" purlVCSURLQualifier = "vcs_url"
// this qualifier is not in the pURL spec, but is used by grype to perform indirect matching based on source information
purlUpstreamQualifier = "upstream"
) )
type urlIdentifier interface { type urlIdentifier interface {