mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
Source distribution external reference utilities and test coverage
Signed-off-by: swadaptive <268603322+swadaptive@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/file"
|
||||
syftFile "github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -86,7 +87,83 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
}
|
||||
|
||||
func encodeSourcePackageExternalReference(p pkg.Package) *cyclonedx.ExternalReference {
|
||||
name, version := sourceCoordinates(p)
|
||||
if name == "" || version == "" {
|
||||
return nil
|
||||
}
|
||||
if p.PURL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkgPurl, err := packageurl.FromString(p.PURL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
qualifiers := pkgPurl.Qualifiers.Map()
|
||||
delete(qualifiers, pkg.PURLQualifierUpstream)
|
||||
qualifiers[pkg.PURLQualifierArch] = "source"
|
||||
|
||||
srcPurl := packageurl.NewPackageURL(
|
||||
pkgPurl.Type,
|
||||
pkgPurl.Namespace,
|
||||
name,
|
||||
version,
|
||||
packageurl.QualifiersFromMap(qualifiers),
|
||||
pkgPurl.Subpath,
|
||||
)
|
||||
|
||||
return &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution,
|
||||
URL: srcPurl.ToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func sourceCoordinates(p pkg.Package) (name, version string) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkDBEntry:
|
||||
if metadata.OriginPackage != "" && metadata.OriginPackage != metadata.Package {
|
||||
return metadata.OriginPackage, metadata.Version
|
||||
}
|
||||
case pkg.DpkgDBEntry:
|
||||
return dpkgSourceCoordinates(metadata)
|
||||
case pkg.DpkgArchiveEntry:
|
||||
return dpkgSourceCoordinates(pkg.DpkgDBEntry(metadata))
|
||||
case pkg.RpmDBEntry:
|
||||
return rpmSourceCoordinates(metadata.SourceRpm)
|
||||
case pkg.RpmArchive:
|
||||
return rpmSourceCoordinates(metadata.SourceRpm)
|
||||
case pkg.AlpmDBEntry:
|
||||
if metadata.BasePackage != "" {
|
||||
return metadata.BasePackage, metadata.Version
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func dpkgSourceCoordinates(entry pkg.DpkgDBEntry) (name, version string) {
|
||||
if entry.Source == "" {
|
||||
return "", ""
|
||||
}
|
||||
if entry.SourceVersion != "" {
|
||||
return entry.Source, entry.SourceVersion
|
||||
}
|
||||
return entry.Source, entry.Version
|
||||
}
|
||||
|
||||
func rpmSourceCoordinates(sourceRpm string) (name, version string) {
|
||||
// <name>-<version>-<release>.src.rpm
|
||||
sourceRpm = strings.TrimSuffix(sourceRpm, ".rpm")
|
||||
sourceRpm = strings.TrimSuffix(strings.TrimSuffix(sourceRpm, ".src"), ".nosrc")
|
||||
release := strings.LastIndex(sourceRpm, "-")
|
||||
if release < 0 {
|
||||
return "", ""
|
||||
}
|
||||
ver := strings.LastIndex(sourceRpm[:release], "-")
|
||||
if ver < 0 {
|
||||
return "", ""
|
||||
}
|
||||
return sourceRpm[:ver], sourceRpm[ver+1:]
|
||||
}
|
||||
|
||||
// supported algorithm in cycloneDX as of 1.4
|
||||
|
||||
@@ -156,6 +156,21 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
input: pkg.Package{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no PURL",
|
||||
input: pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.0-r1",
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "some-package",
|
||||
OriginPackage: "some-package",
|
||||
Version: "1.0.0-r1",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
@@ -171,11 +186,28 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expected: &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:apk/alpine/libc-dev@0.7.2-r3&arch=source&distro=alpine-3.16.3",
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:apk/alpine/libc-dev@0.7.2-r3?arch=source&distro=alpine-3.16.3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
// Matching existing implementation where upstream qualifier is omitted when OriginPackage = Package for Alpine
|
||||
name: "from apk with source = bin",
|
||||
input: pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.0-r1",
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: "pkg:apk/alpine/some-package@1.0.0-r1?arch=x86_64&distro=alpine-3.16.3",
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "some-package",
|
||||
OriginPackage: "some-package",
|
||||
Version: "1.0.0-r1",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "from deb",
|
||||
input: pkg.Package{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.5.2-6+deb12u1",
|
||||
@@ -193,7 +225,7 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from dpkg with source version",
|
||||
name: "from deb with source version",
|
||||
input: pkg.Package{
|
||||
Name: "attr",
|
||||
Version: "1:2.4.47-2+b1",
|
||||
@@ -211,6 +243,39 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:deb/debian/attr@1%3A2.4.47-2?arch=source&distro=debian-12",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from deb with empty source",
|
||||
input: pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.DebPkg,
|
||||
PURL: "pkg:deb/debian/some-package@1.0.0?arch=all&distro=debian-12",
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "some-package",
|
||||
Version: "1.0.0",
|
||||
Architecture: "all",
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "from deb archive",
|
||||
input: pkg.Package{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.5.2-6+deb12u1",
|
||||
Type: pkg.DebPkg,
|
||||
PURL: "pkg:deb/libpam-runtime@1.5.2-6%2Bdeb12u1?arch=all&upstream=pam",
|
||||
Metadata: pkg.DpkgArchiveEntry{
|
||||
Package: "libpam-runtime",
|
||||
Version: "1.5.2-6+deb12u1",
|
||||
Source: "pam",
|
||||
Architecture: "all",
|
||||
},
|
||||
},
|
||||
expected: &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:deb/pam@1.5.2-6%2Bdeb12u1?arch=source",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
@@ -231,6 +296,46 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/centos/bind@9.11.13-3.el8?arch=source&distro=centos-8&epoch=32",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from rpm with source = bin",
|
||||
input: pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "32:1.0.0-1.el8",
|
||||
Type: pkg.RpmPkg,
|
||||
PURL: "pkg:rpm/centos/some-package@1.0.0-1.el8?arch=x86_64&distro=centos-8&epoch=32&upstream=some-package-1.0.0-1.el8.src.rpm",
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Name: "some-package",
|
||||
Version: "1.0.0",
|
||||
Release: "1.el8",
|
||||
Epoch: &rpm_epoch,
|
||||
Arch: "x86_64",
|
||||
SourceRpm: "some-package-1.0.0-1.el8.src.rpm",
|
||||
},
|
||||
},
|
||||
expected: &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/centos/some-package@1.0.0-1.el8?arch=source&distro=centos-8&epoch=32",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from rpm archive",
|
||||
input: pkg.Package{
|
||||
Name: "bind-export-libs",
|
||||
Version: "32:9.11.13-3.el8",
|
||||
Type: pkg.RpmPkg,
|
||||
PURL: "pkg:rpm/bind-export-libs@9.11.13-3.el8?arch=x86_64&epoch=32&upstream=bind-9.11.13-3.el8.src.rpm",
|
||||
Metadata: pkg.RpmArchive{
|
||||
Name: "bind-export-libs",
|
||||
Version: "9.11.13",
|
||||
Release: "3.el8",
|
||||
Epoch: &rpm_epoch,
|
||||
Arch: "x86_64",
|
||||
SourceRpm: "bind-9.11.13-3.el8.src.rpm",
|
||||
},
|
||||
},
|
||||
expected: &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:rpm/bind@9.11.13-3.el8?arch=source&epoch=32",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "from alpm",
|
||||
input: pkg.Package{
|
||||
@@ -249,6 +354,26 @@ func Test_encodeSourcePackage(t *testing.T) {
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:alpm/arch/gcc@13.2.1-3?arch=source&distro=arch-rolling",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Matching existing implementation where upstream qualifier is *not* omitted when BasePackage == Package for Alpm
|
||||
// This differs from the Alpine case where it is omitted.
|
||||
name: "from alpm with source = bin",
|
||||
input: pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.0",
|
||||
Type: pkg.AlpmPkg,
|
||||
PURL: "pkg:alpm/arch/some-package@1.0.0?arch=x86_64&distro=arch-rolling&upstream=some-package",
|
||||
Metadata: pkg.AlpmDBEntry{
|
||||
Package: "some-package",
|
||||
Version: "1.0.0",
|
||||
BasePackage: "some-package",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
expected: &cyclonedx.ExternalReference{
|
||||
Type: cyclonedx.ERTypeSourceDistribution, URL: "pkg:alpm/arch/some-package@1.0.0?arch=source&distro=arch-rolling",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user