Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode upstream qualifier on OS package pURLs #769

Merged
merged 1 commit into from Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions syft/pkg/apk_metadata.go
Expand Up @@ -49,6 +49,14 @@ type ApkFileRecord struct {

// 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 {
qualifiers := map[string]string{
purlArchQualifier: m.Architecture,
}

if m.OriginPackage != "" {
qualifiers[purlUpstreamQualifier] = m.OriginPackage
}

return packageurl.NewPackageURL(
// note: this is currently a candidate and not technically within spec
// see https://github.com/package-url/purl-spec#other-candidate-types-to-define
Expand All @@ -57,9 +65,7 @@ func (m ApkMetadata) PackageURL(distro *linux.Release) string {
m.Package,
m.Version,
purlQualifiers(
map[string]string{
purlArchQualifier: m.Architecture,
},
qualifiers,
distro,
),
"",
Expand Down
16 changes: 15 additions & 1 deletion syft/pkg/apk_metadata_test.go
Expand Up @@ -31,7 +31,7 @@ func TestApkMetadata_pURL(t *testing.T) {
expected: "pkg:alpine/p@v?arch=a&distro=alpine-3.4.6",
},
{
name: "missing architecure",
name: "missing architecture",
metadata: ApkMetadata{
Package: "p",
Version: "v",
Expand Down Expand Up @@ -67,6 +67,20 @@ func TestApkMetadata_pURL(t *testing.T) {
},
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 {
Expand Down
18 changes: 15 additions & 3 deletions syft/pkg/dpkg_metadata.go
@@ -1,6 +1,7 @@
package pkg

import (
"fmt"
"sort"

"github.com/anchore/syft/syft/file"
Expand Down Expand Up @@ -43,6 +44,19 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
if distro != nil {
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(
// 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.
Expand All @@ -51,9 +65,7 @@ func (m DpkgMetadata) PackageURL(distro *linux.Release) string {
m.Package,
m.Version,
purlQualifiers(
map[string]string{
purlArchQualifier: m.Architecture,
},
qualifiers,
distro,
),
"",
Expand Down
30 changes: 27 additions & 3 deletions syft/pkg/dpkg_metadata_test.go
Expand Up @@ -25,7 +25,6 @@ func TestDpkgMetadata_pURL(t *testing.T) {
},
metadata: DpkgMetadata{
Package: "p",
Source: "s",
Version: "v",
},
expected: "pkg:deb/debian/p@v?distro=debian-11",
Expand All @@ -38,7 +37,6 @@ func TestDpkgMetadata_pURL(t *testing.T) {
},
metadata: DpkgMetadata{
Package: "p",
Source: "s",
Version: "v",
Architecture: "a",
},
Expand All @@ -48,11 +46,37 @@ func TestDpkgMetadata_pURL(t *testing.T) {
name: "missing distro",
metadata: DpkgMetadata{
Package: "p",
Source: "s",
Version: "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 {
Expand Down
2 changes: 1 addition & 1 deletion syft/pkg/python_package_metadata.go
Expand Up @@ -106,7 +106,7 @@ func (p PythonDirectURLOriginInfo) vcsURLQualifier() packageurl.Qualifiers {
if p.VCS != "" {
// 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
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
}
4 changes: 4 additions & 0 deletions syft/pkg/rpmdb_metadata.go
Expand Up @@ -63,6 +63,10 @@ func (m RpmdbMetadata) PackageURL(distro *linux.Release) string {
qualifiers[purlEpochQualifier] = strconv.Itoa(*m.Epoch)
}

if m.SourceRpm != "" {
qualifiers[purlUpstreamQualifier] = m.SourceRpm
}

return packageurl.NewPackageURL(
packageurl.TypeRPM,
namespace,
Expand Down
36 changes: 25 additions & 11 deletions syft/pkg/rpmdb_metadata_test.go
Expand Up @@ -18,33 +18,33 @@ func TestRpmMetadata_pURL(t *testing.T) {
expected string
}{
{
name: "with arch and epoch",
name: "go case",
distro: &linux.Release{
ID: "centos",
VersionID: "7",
ID: "rhel",
VersionID: "8.4",
},
metadata: RpmdbMetadata{
Name: "p",
Version: "v",
Arch: "a",
Release: "r",
Epoch: intRef(1),
Epoch: nil,
},
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7",
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
},
{
name: "go case",
name: "with arch and epoch",
distro: &linux.Release{
ID: "rhel",
VersionID: "8.4",
ID: "centos",
VersionID: "7",
},
metadata: RpmdbMetadata{
Name: "p",
Version: "v",
Arch: "a",
Release: "r",
Epoch: nil,
Epoch: intRef(1),
},
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7",
},
{
name: "missing distro",
Expand All @@ -56,6 +56,20 @@ func TestRpmMetadata_pURL(t *testing.T) {
},
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 {
Expand Down
5 changes: 4 additions & 1 deletion syft/pkg/url.go
Expand Up @@ -14,7 +14,10 @@ const (
purlArchQualifier = "arch"
purlDistroQualifier = "distro"
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 {
Expand Down