mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
port rpm cataloger to new generic cataloger pattern (#1321)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
1076281566
commit
3048382bbd
@ -41,7 +41,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
javascript.NewJavascriptPackageCataloger(),
|
||||
javascript.NewNodeBinaryCataloger(),
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpm.NewRpmdbCataloger(),
|
||||
rpm.NewRpmDBCataloger(),
|
||||
java.NewJavaCataloger(cfg.Java()),
|
||||
apkdb.NewApkdbCataloger(),
|
||||
golang.NewGoModuleBinaryCataloger(),
|
||||
@ -61,7 +61,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
||||
javascript.NewJavascriptLockCataloger(),
|
||||
javascript.NewNodeBinaryCataloger(),
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpm.NewRpmdbCataloger(),
|
||||
rpm.NewRpmDBCataloger(),
|
||||
rpm.NewFileCataloger(),
|
||||
java.NewJavaCataloger(cfg.Java()),
|
||||
java.NewJavaPomCataloger(),
|
||||
@ -90,7 +90,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
javascript.NewJavascriptPackageCataloger(),
|
||||
javascript.NewNodeBinaryCataloger(),
|
||||
deb.NewDpkgdbCataloger(),
|
||||
rpm.NewRpmdbCataloger(),
|
||||
rpm.NewRpmDBCataloger(),
|
||||
rpm.NewFileCataloger(),
|
||||
java.NewJavaCataloger(cfg.Java()),
|
||||
java.NewJavaPomCataloger(),
|
||||
|
||||
22
syft/pkg/cataloger/rpm/cataloger.go
Normal file
22
syft/pkg/cataloger/rpm/cataloger.go
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
Package rpm provides a concrete DBCataloger implementation for RPM "Package" DB files and a FileCataloger for RPM files.
|
||||
*/
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewRpmDBCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmDBCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("rpm-db-cataloger").
|
||||
WithParserByGlobs(parseRpmDB, pkg.RpmDBGlob).
|
||||
WithParserByGlobs(parseRpmManifest, pkg.RpmManifestGlob)
|
||||
}
|
||||
|
||||
// NewFileCataloger returns a new RPM file cataloger object.
|
||||
func NewFileCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("rpm-file-cataloger").
|
||||
WithParserByGlobs(parseRpm, "**/*.rpm")
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
/*
|
||||
Package rpm provides a concrete DBCataloger implementation for RPM "Package" DB files
|
||||
and a FileCataloger for RPM files.
|
||||
*/
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const dbCatalogerName = "rpm-db-cataloger"
|
||||
|
||||
type DBCataloger struct{}
|
||||
|
||||
// NewRpmdbCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmdbCataloger() *DBCataloger {
|
||||
return &DBCataloger{}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
func (c *DBCataloger) Name() string {
|
||||
return dbCatalogerName
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||
func (c *DBCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
fileMatches, err := resolver.FilesByGlob(pkg.RpmDBGlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find rpmdb's by glob: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, location := range fileMatches {
|
||||
dbContentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
discoveredPkgs, err := parseRpmDB(resolver, location, dbContentReader)
|
||||
internal.CloseAndLogError(dbContentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to catalog rpmdb package=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, discoveredPkgs...)
|
||||
}
|
||||
|
||||
// Additionally look for RPM manifest files to detect packages in CBL-Mariner distroless images
|
||||
manifestFileMatches, err := resolver.FilesByGlob(pkg.RpmManifestGlob)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find rpm manifests by glob: %w", err)
|
||||
}
|
||||
|
||||
for _, location := range manifestFileMatches {
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
discoveredPkgs, err := parseRpmManifest(location, reader)
|
||||
internal.CloseAndLogError(reader, location.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to catalog rpm manifest=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, discoveredPkgs...)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
"github.com/sassoftware/go-rpmutils"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type FileCataloger struct{}
|
||||
|
||||
// NewFileCataloger returns a new RPM file cataloger object.
|
||||
func NewFileCataloger() *FileCataloger {
|
||||
return &FileCataloger{}
|
||||
}
|
||||
|
||||
// Name returns a string that uniquely describes a cataloger
|
||||
func (c *FileCataloger) Name() string {
|
||||
return "rpm-file-cataloger"
|
||||
}
|
||||
|
||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm files
|
||||
//
|
||||
//nolint:funlen
|
||||
func (c *FileCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
fileMatches, err := resolver.FilesByGlob("**/*.rpm")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find rpm files's by glob: %w", err)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, location := range fileMatches {
|
||||
contentReader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rpm, err := rpmutils.ReadRpm(contentReader)
|
||||
if err != nil {
|
||||
log.Debugf("RPM file found but unable to read: %s (%v)", location.RealPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
nevra, err := rpm.Header.GetNEVRA()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
licenses, _ := rpm.Header.GetStrings(rpmutils.LICENSE)
|
||||
sourceRpm, _ := rpm.Header.GetString(rpmutils.SOURCERPM)
|
||||
vendor, _ := rpm.Header.GetString(rpmutils.VENDOR)
|
||||
digestAlgorithm := getDigestAlgorithm(rpm.Header)
|
||||
size, _ := rpm.Header.InstalledSize()
|
||||
files, _ := rpm.Header.GetFiles()
|
||||
|
||||
p := pkg.Package{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
FoundBy: c.Name(),
|
||||
Licenses: licenses,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Vendor: vendor,
|
||||
License: strings.Join(licenses, " AND "),
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
},
|
||||
}
|
||||
p.SetID()
|
||||
pkgs = append(pkgs, p)
|
||||
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to catalog rpm file=%+v: %w", location.RealPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func getDigestAlgorithm(header *rpmutils.RpmHeader) string {
|
||||
digestAlgorithm, _ := header.GetString(rpmutils.FILEDIGESTALGO)
|
||||
if digestAlgorithm != "" {
|
||||
return digestAlgorithm
|
||||
}
|
||||
digestAlgorithms, _ := header.GetUint32s(rpmutils.FILEDIGESTALGO)
|
||||
if len(digestAlgorithms) > 0 {
|
||||
digestAlgo := int(digestAlgorithms[0])
|
||||
return rpmutils.GetFileAlgoName(digestAlgo)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mapFiles(files []rpmutils.FileInfo, digestAlgorithm string) []pkg.RpmdbFileRecord {
|
||||
var out []pkg.RpmdbFileRecord
|
||||
for _, f := range files {
|
||||
digest := file.Digest{}
|
||||
if f.Digest() != "" {
|
||||
digest = file.Digest{
|
||||
Algorithm: digestAlgorithm,
|
||||
Value: f.Digest(),
|
||||
}
|
||||
}
|
||||
out = append(out, pkg.RpmdbFileRecord{
|
||||
Path: f.Name(),
|
||||
Mode: pkg.RpmdbFileMode(f.Mode()),
|
||||
Size: int(f.Size()),
|
||||
Digest: digest,
|
||||
UserName: f.UserName(),
|
||||
GroupName: f.GroupName(),
|
||||
Flags: rpmdb.FileFlags(f.Flags()).String(),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseEpoch(epoch string) *int {
|
||||
i, err := strconv.Atoi(epoch)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &i
|
||||
}
|
||||
122
syft/pkg/cataloger/rpm/package.go
Normal file
122
syft/pkg/cataloger/rpm/package.go
Normal file
@ -0,0 +1,122 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(dbLocation source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: toELVersion(metadata),
|
||||
PURL: packageURL(metadata, distro),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if metadata.License != "" {
|
||||
p.Licenses = append(p.Licenses, metadata.License)
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
|
||||
func newMetadataFromEntry(entry rpmdb.PackageInfo, files []pkg.RpmdbFileRecord) pkg.RpmMetadata {
|
||||
return pkg.RpmMetadata{
|
||||
Name: entry.Name,
|
||||
Version: entry.Version,
|
||||
Epoch: entry.Epoch,
|
||||
Arch: entry.Arch,
|
||||
Release: entry.Release,
|
||||
SourceRpm: entry.SourceRpm,
|
||||
Vendor: entry.Vendor,
|
||||
License: entry.License,
|
||||
Size: entry.Size,
|
||||
ModularityLabel: entry.Modularitylabel,
|
||||
Files: files,
|
||||
}
|
||||
}
|
||||
|
||||
func newMetadataFromManifestLine(entry string) (*pkg.RpmMetadata, error) {
|
||||
parts := strings.Split(entry, "\t")
|
||||
if len(parts) < 10 {
|
||||
return nil, fmt.Errorf("unexpected number of fields in line: %s", entry)
|
||||
}
|
||||
|
||||
versionParts := strings.Split(parts[1], "-")
|
||||
if len(versionParts) != 2 {
|
||||
return nil, fmt.Errorf("unexpected version field: %s", parts[1])
|
||||
}
|
||||
version := versionParts[0]
|
||||
release := versionParts[1]
|
||||
|
||||
converted, err := strconv.Atoi(parts[8])
|
||||
var epoch *int
|
||||
if err != nil || parts[5] == "(none)" {
|
||||
epoch = nil
|
||||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
|
||||
converted, err = strconv.Atoi(parts[6])
|
||||
var size int
|
||||
if err == nil {
|
||||
size = converted
|
||||
}
|
||||
|
||||
return &pkg.RpmMetadata{
|
||||
Name: parts[0],
|
||||
Version: version,
|
||||
Epoch: epoch,
|
||||
Arch: parts[7],
|
||||
Release: release,
|
||||
SourceRpm: parts[9],
|
||||
Vendor: parts[4],
|
||||
Size: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// packageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(m pkg.RpmMetadata, distro *linux.Release) string {
|
||||
var namespace string
|
||||
if distro != nil {
|
||||
namespace = distro.ID
|
||||
}
|
||||
|
||||
qualifiers := map[string]string{
|
||||
pkg.PURLQualifierArch: m.Arch,
|
||||
}
|
||||
|
||||
if m.Epoch != nil {
|
||||
qualifiers[pkg.PURLQualifierEpoch] = strconv.Itoa(*m.Epoch)
|
||||
}
|
||||
|
||||
if m.SourceRpm != "" {
|
||||
qualifiers[pkg.PURLQualifierUpstream] = m.SourceRpm
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeRPM,
|
||||
namespace,
|
||||
m.Name,
|
||||
// for purl the epoch is a qualifier, not part of the version
|
||||
// see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst under the RPM section
|
||||
fmt.Sprintf("%s-%s", m.Version, m.Release),
|
||||
pkg.PURLQualifiers(
|
||||
qualifiers,
|
||||
distro,
|
||||
),
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
84
syft/pkg/cataloger/rpm/package_test.go
Normal file
84
syft/pkg/cataloger/rpm/package_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_packageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
distro *linux.Release
|
||||
metadata pkg.RpmMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
distro: &linux.Release{
|
||||
ID: "rhel",
|
||||
VersionID: "8.4",
|
||||
},
|
||||
metadata: pkg.RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Release: "r",
|
||||
Epoch: nil,
|
||||
},
|
||||
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
|
||||
},
|
||||
{
|
||||
name: "with arch and epoch",
|
||||
distro: &linux.Release{
|
||||
ID: "centos",
|
||||
VersionID: "7",
|
||||
},
|
||||
metadata: pkg.RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Arch: "a",
|
||||
Release: "r",
|
||||
Epoch: intRef(1),
|
||||
},
|
||||
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7",
|
||||
},
|
||||
{
|
||||
name: "missing distro",
|
||||
metadata: pkg.RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Release: "r",
|
||||
Epoch: nil,
|
||||
},
|
||||
expected: "pkg:rpm/p@v-r",
|
||||
},
|
||||
{
|
||||
name: "with upstream source rpm info",
|
||||
distro: &linux.Release{
|
||||
ID: "rhel",
|
||||
VersionID: "8.4",
|
||||
},
|
||||
metadata: pkg.RpmMetadata{
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := packageURL(test.metadata, test.distro)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
95
syft/pkg/cataloger/rpm/parse_rpm.go
Normal file
95
syft/pkg/cataloger/rpm/parse_rpm.go
Normal file
@ -0,0 +1,95 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
"github.com/sassoftware/go-rpmutils"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// parseRpm parses a single RPM
|
||||
func parseRpm(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
rpm, err := rpmutils.ReadRpm(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("RPM file found but unable to read: %s (%v)", reader.Location.RealPath, err)
|
||||
}
|
||||
|
||||
nevra, err := rpm.Header.GetNEVRA()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
licenses, _ := rpm.Header.GetStrings(rpmutils.LICENSE)
|
||||
sourceRpm, _ := rpm.Header.GetString(rpmutils.SOURCERPM)
|
||||
vendor, _ := rpm.Header.GetString(rpmutils.VENDOR)
|
||||
digestAlgorithm := getDigestAlgorithm(rpm.Header)
|
||||
size, _ := rpm.Header.InstalledSize()
|
||||
files, _ := rpm.Header.GetFiles()
|
||||
|
||||
metadata := pkg.RpmMetadata{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Vendor: vendor,
|
||||
License: strings.Join(licenses, " AND "), // TODO: AND conjunction is not necessarily correct, but we don't have a way to represent multiple licenses yet
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
}
|
||||
|
||||
return []pkg.Package{newPackage(reader.Location, metadata, nil)}, nil, nil
|
||||
}
|
||||
|
||||
func getDigestAlgorithm(header *rpmutils.RpmHeader) string {
|
||||
digestAlgorithm, _ := header.GetString(rpmutils.FILEDIGESTALGO)
|
||||
if digestAlgorithm != "" {
|
||||
return digestAlgorithm
|
||||
}
|
||||
digestAlgorithms, _ := header.GetUint32s(rpmutils.FILEDIGESTALGO)
|
||||
if len(digestAlgorithms) > 0 {
|
||||
digestAlgo := int(digestAlgorithms[0])
|
||||
return rpmutils.GetFileAlgoName(digestAlgo)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mapFiles(files []rpmutils.FileInfo, digestAlgorithm string) []pkg.RpmdbFileRecord {
|
||||
var out []pkg.RpmdbFileRecord
|
||||
for _, f := range files {
|
||||
digest := file.Digest{}
|
||||
if f.Digest() != "" {
|
||||
digest = file.Digest{
|
||||
Algorithm: digestAlgorithm,
|
||||
Value: f.Digest(),
|
||||
}
|
||||
}
|
||||
out = append(out, pkg.RpmdbFileRecord{
|
||||
Path: f.Name(),
|
||||
Mode: pkg.RpmdbFileMode(f.Mode()),
|
||||
Size: int(f.Size()),
|
||||
Digest: digest,
|
||||
UserName: f.UserName(),
|
||||
GroupName: f.GroupName(),
|
||||
Flags: rpmdb.FileFlags(f.Flags()).String(),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseEpoch(epoch string) *int {
|
||||
i, err := strconv.Atoi(epoch)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &i
|
||||
}
|
||||
@ -9,16 +9,19 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it.
|
||||
func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseRpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
f, err := os.CreateTemp("", internal.ApplicationName+"-rpmdb")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to create temp rpmdb file: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@ -30,26 +33,40 @@ func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, re
|
||||
|
||||
_, err = io.Copy(f, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to copy rpmdb contents to temp file: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed to copy rpmdb contents to temp file: %w", err)
|
||||
}
|
||||
|
||||
db, err := rpmdb.Open(f.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkgList, err := db.ListPackages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var allPkgs []pkg.Package
|
||||
|
||||
var distro *linux.Release
|
||||
if env != nil {
|
||||
distro = env.LinuxRelease
|
||||
}
|
||||
|
||||
for _, entry := range pkgList {
|
||||
p := newPkg(resolver, dbLocation, entry)
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
p := newPackage(
|
||||
reader.Location,
|
||||
newMetadataFromEntry(*entry, extractRpmdbFileRecords(resolver, *entry)),
|
||||
distro,
|
||||
)
|
||||
|
||||
if !pkg.IsValid(&p) {
|
||||
log.Warnf("ignoring invalid package found in RPM DB: location=%q name=%q version=%q", dbLocation, entry.Name, entry.Version)
|
||||
log.WithFields("location", reader.RealPath, "pkg", fmt.Sprintf("%s@%s", entry.Name, entry.Version)).
|
||||
Warn("ignoring invalid package found in RPM DB")
|
||||
continue
|
||||
}
|
||||
|
||||
@ -57,40 +74,7 @@ func parseRpmDB(resolver source.FilePathResolver, dbLocation source.Location, re
|
||||
allPkgs = append(allPkgs, p)
|
||||
}
|
||||
|
||||
return allPkgs, nil
|
||||
}
|
||||
|
||||
func newPkg(resolver source.FilePathResolver, dbLocation source.Location, entry *rpmdb.PackageInfo) pkg.Package {
|
||||
metadata := pkg.RpmMetadata{
|
||||
Name: entry.Name,
|
||||
Version: entry.Version,
|
||||
Epoch: entry.Epoch,
|
||||
Arch: entry.Arch,
|
||||
Release: entry.Release,
|
||||
SourceRpm: entry.SourceRpm,
|
||||
Vendor: entry.Vendor,
|
||||
License: entry.License,
|
||||
Size: entry.Size,
|
||||
ModularityLabel: entry.Modularitylabel,
|
||||
Files: extractRpmdbFileRecords(resolver, entry),
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: entry.Name,
|
||||
Version: toELVersion(metadata),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if entry.License != "" {
|
||||
p.Licenses = append(p.Licenses, entry.License)
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return p
|
||||
return allPkgs, nil, nil
|
||||
}
|
||||
|
||||
// The RPM naming scheme is [name]-[version]-[release]-[arch], where version is implicitly expands to [epoch]:[version].
|
||||
@ -106,7 +90,7 @@ func toELVersion(metadata pkg.RpmMetadata) string {
|
||||
return fmt.Sprintf("%s-%s", metadata.Version, metadata.Release)
|
||||
}
|
||||
|
||||
func extractRpmdbFileRecords(resolver source.FilePathResolver, entry *rpmdb.PackageInfo) []pkg.RpmdbFileRecord {
|
||||
func extractRpmdbFileRecords(resolver source.FilePathResolver, entry rpmdb.PackageInfo) []pkg.RpmdbFileRecord {
|
||||
var records = make([]pkg.RpmdbFileRecord, 0)
|
||||
|
||||
files, err := entry.InstalledFiles()
|
||||
@ -2,21 +2,38 @@ package rpm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var _ source.FileResolver = (*rpmdbTestFileResolverMock)(nil)
|
||||
|
||||
type rpmdbTestFileResolverMock struct {
|
||||
ignorePaths bool
|
||||
}
|
||||
|
||||
func (r rpmdbTestFileResolverMock) FileContentsByLocation(location source.Location) (io.ReadCloser, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (r rpmdbTestFileResolverMock) AllLocations() <-chan source.Location {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (r rpmdbTestFileResolverMock) FileMetadataByLocation(location source.Location) (source.FileMetadata, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func newTestFileResolver(ignorePaths bool) *rpmdbTestFileResolverMock {
|
||||
return &rpmdbTestFileResolverMock{
|
||||
ignorePaths: ignorePaths,
|
||||
@ -54,23 +71,21 @@ func (r *rpmdbTestFileResolverMock) FilesByMIMEType(...string) ([]source.Locatio
|
||||
}
|
||||
|
||||
func TestParseRpmDB(t *testing.T) {
|
||||
dbLocation := source.NewLocation("test-path")
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected map[string]pkg.Package
|
||||
expected []pkg.Package
|
||||
ignorePaths bool
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we DO NOT expect a path)
|
||||
ignorePaths: true,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
FoundBy: dbCatalogerName,
|
||||
PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm",
|
||||
Locations: source.NewLocationSet(source.NewLocation("test-fixtures/Packages")),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
@ -93,12 +108,12 @@ func TestParseRpmDB(t *testing.T) {
|
||||
fixture: "test-fixtures/Packages",
|
||||
// we only surface package paths for files that exist (here we expect a path)
|
||||
ignorePaths: false,
|
||||
expected: map[string]pkg.Package{
|
||||
"dive": {
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
FoundBy: dbCatalogerName,
|
||||
PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm",
|
||||
Locations: source.NewLocationSet(source.NewLocation("test-fixtures/Packages")),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
@ -132,34 +147,11 @@ func TestParseRpmDB(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
fixture, err := os.Open(test.fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
fileResolver := newTestFileResolver(test.ignorePaths)
|
||||
|
||||
actual, err := parseRpmDB(fileResolver, dbLocation, fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpmdb: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != len(test.expected) {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
e := test.expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
WithResolver(newTestFileResolver(test.ignorePaths)).
|
||||
FromFile(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
TestParser(t, parseRpmDB)
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,74 +2,18 @@ package rpm
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Parses an entry in an RPM manifest file as used in Mariner distroless containers
|
||||
// Each line is the output of :
|
||||
// rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n"
|
||||
// https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150
|
||||
func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package, error) {
|
||||
parts := strings.Split(entry, "\t")
|
||||
if len(parts) < 10 {
|
||||
return nil, fmt.Errorf("unexpected number of fields in line: %s", entry)
|
||||
}
|
||||
|
||||
versionParts := strings.Split(parts[1], "-")
|
||||
if len(versionParts) != 2 {
|
||||
return nil, fmt.Errorf("unexpected version field: %s", parts[1])
|
||||
}
|
||||
version := versionParts[0]
|
||||
release := versionParts[1]
|
||||
|
||||
converted, err := strconv.Atoi(parts[8])
|
||||
var epoch *int
|
||||
if err != nil || parts[5] == "(none)" {
|
||||
epoch = nil
|
||||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
|
||||
converted, err = strconv.Atoi(parts[6])
|
||||
var size int
|
||||
if err == nil {
|
||||
size = converted
|
||||
}
|
||||
|
||||
metadata := pkg.RpmMetadata{
|
||||
Name: parts[0],
|
||||
Version: version,
|
||||
Epoch: epoch,
|
||||
Arch: parts[7],
|
||||
Release: release,
|
||||
SourceRpm: parts[9],
|
||||
Vendor: parts[4],
|
||||
Size: size,
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: parts[0],
|
||||
Version: toELVersion(metadata),
|
||||
Locations: source.NewLocationSet(location),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Parses an RPM manifest file, as used in Mariner distroless containers, and returns the Packages listed
|
||||
func parseRpmManifest(dbLocation source.Location, reader io.Reader) ([]pkg.Package, error) {
|
||||
func parseRpmManifest(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
r := bufio.NewReader(reader)
|
||||
allPkgs := make([]pkg.Package, 0)
|
||||
|
||||
@ -79,14 +23,14 @@ func parseRpmManifest(dbLocation source.Location, reader io.Reader) ([]pkg.Packa
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
p, err := parseRpmManifestEntry(strings.TrimSuffix(line, "\n"), dbLocation)
|
||||
p, err := parseRpmManifestEntry(strings.TrimSuffix(line, "\n"), reader.Location)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse RPM manifest entry: %w", err)
|
||||
continue
|
||||
@ -100,5 +44,24 @@ func parseRpmManifest(dbLocation source.Location, reader io.Reader) ([]pkg.Packa
|
||||
allPkgs = append(allPkgs, *p)
|
||||
}
|
||||
|
||||
return allPkgs, nil
|
||||
return allPkgs, nil, nil
|
||||
}
|
||||
|
||||
// Parses an entry in an RPM manifest file as used in Mariner distroless containers
|
||||
// Each line is the output of :
|
||||
// rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n"
|
||||
// https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150
|
||||
func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package, error) {
|
||||
metadata, err := newMetadataFromManifestLine(entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if metadata == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
p := newPackage(location, *metadata, nil)
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
@ -1,25 +1,22 @@
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseRpmManifest(t *testing.T) {
|
||||
location := source.NewLocation("test-path")
|
||||
|
||||
fixture_path := "test-fixtures/container-manifest-2"
|
||||
expected := map[string]pkg.Package{
|
||||
"mariner-release": {
|
||||
fixture := "test-fixtures/container-manifest-2"
|
||||
location := source.NewLocation(fixture)
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "mariner-release",
|
||||
Version: "2.0-12.cm2",
|
||||
PURL: "pkg:rpm/mariner-release@2.0-12.cm2?arch=noarch&upstream=mariner-release-2.0-12.cm2.src.rpm",
|
||||
Locations: source.NewLocationSet(location),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
@ -33,11 +30,11 @@ func TestParseRpmManifest(t *testing.T) {
|
||||
Vendor: "Microsoft Corporation",
|
||||
},
|
||||
},
|
||||
"filesystem": {
|
||||
{
|
||||
Name: "filesystem",
|
||||
Version: "1.1-9.cm2",
|
||||
PURL: "pkg:rpm/filesystem@1.1-9.cm2?arch=x86_64&upstream=filesystem-1.1-9.cm2.src.rpm",
|
||||
Locations: source.NewLocationSet(location),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
@ -51,11 +48,11 @@ func TestParseRpmManifest(t *testing.T) {
|
||||
Vendor: "Microsoft Corporation",
|
||||
},
|
||||
},
|
||||
"glibc": {
|
||||
{
|
||||
Name: "glibc",
|
||||
Version: "2.35-2.cm2",
|
||||
PURL: "pkg:rpm/glibc@2.35-2.cm2?arch=x86_64&upstream=glibc-2.35-2.cm2.src.rpm",
|
||||
Locations: source.NewLocationSet(location),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
@ -69,11 +66,11 @@ func TestParseRpmManifest(t *testing.T) {
|
||||
Vendor: "Microsoft Corporation",
|
||||
},
|
||||
},
|
||||
"openssl-libs": {
|
||||
{
|
||||
Name: "openssl-libs",
|
||||
Version: "1.1.1k-15.cm2",
|
||||
PURL: "pkg:rpm/openssl-libs@1.1.1k-15.cm2?arch=x86_64&upstream=openssl-1.1.1k-15.cm2.src.rpm",
|
||||
Locations: source.NewLocationSet(location),
|
||||
FoundBy: dbCatalogerName,
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
@ -89,30 +86,9 @@ func TestParseRpmManifest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
fixture, err := os.Open(fixture_path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, fixture).
|
||||
Expects(expected, nil).
|
||||
TestParser(t, parseRpmManifest)
|
||||
|
||||
actual, err := parseRpmManifest(location, fixture)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse rpm manifest: %+v", err)
|
||||
}
|
||||
|
||||
if len(actual) != 12 {
|
||||
for _, a := range actual {
|
||||
t.Log(" ", a)
|
||||
}
|
||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(expected))
|
||||
}
|
||||
|
||||
for _, a := range actual[0:4] {
|
||||
e := expected[a.Name]
|
||||
diffs := deep.Equal(a, e)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,26 +3,25 @@ package rpm
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseRpmFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected map[string]pkg.Package
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/rpms",
|
||||
expected: map[string]pkg.Package{
|
||||
"abc": {
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "abc",
|
||||
Version: "1.01",
|
||||
Locations: source.NewLocationSet(),
|
||||
Version: "0:1.01-9.hg20160905.el7",
|
||||
PURL: "pkg:rpm/abc@1.01-9.hg20160905.el7?arch=x86_64&epoch=0&upstream=abc-1.01-9.hg20160905.el7.src.rpm",
|
||||
Locations: source.NewLocationSet(source.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")),
|
||||
FoundBy: "rpm-file-cataloger",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
@ -46,10 +45,11 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"zork": {
|
||||
{
|
||||
Name: "zork",
|
||||
Version: "1.0.3",
|
||||
Locations: source.NewLocationSet(),
|
||||
Version: "0:1.0.3-1.el7",
|
||||
PURL: "pkg:rpm/zork@1.0.3-1.el7?arch=x86_64&epoch=0&upstream=zork-1.0.3-1.el7.src.rpm",
|
||||
Locations: source.NewLocationSet(source.NewLocation("zork-1.0.3-1.el7.x86_64.rpm")),
|
||||
FoundBy: "rpm-file-cataloger",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
@ -86,24 +86,10 @@ func TestParseRpmFiles(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
s, err := source.NewFromDirectory(test.fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
packages, _, err := NewFileCataloger().Catalog(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, a := range packages {
|
||||
e := test.expected[a.Name]
|
||||
diffs := deep.Equal(e, a)
|
||||
if len(diffs) > 0 {
|
||||
for _, d := range diffs {
|
||||
t.Errorf("diff: %+v", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
TestCataloger(t, NewFileCataloger())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,4 @@ mariner-release 2.0-12.cm2 1653816591 1653753130 Microsoft Corporation (none) 58
|
||||
filesystem 1.1-9.cm2 1653816591 1653628924 Microsoft Corporation (none) 7596 x86_64 0 filesystem-1.1-9.cm2.src.rpm
|
||||
glibc 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 10855265 x86_64 0 glibc-2.35-2.cm2.src.rpm
|
||||
openssl-libs 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 4365048 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
|
||||
libgcc 11.2.0-2.cm2 1653816591 1650702349 Microsoft Corporation (none) 103960 x86_64 0 gcc-11.2.0-2.cm2.src.rpm
|
||||
openssl 1.1.1k-15.cm2 1653816591 1653631609 Microsoft Corporation (none) 1286337 x86_64 0 openssl-1.1.1k-15.cm2.src.rpm
|
||||
glibc-iconv 2.35-2.cm2 1653816591 1653628955 Microsoft Corporation (none) 8397230 x86_64 0 glibc-2.35-2.cm2.src.rpm
|
||||
iana-etc 20211115-1.cm2 1653816591 1650711959 Microsoft Corporation (none) 4380680 noarch 0 iana-etc-20211115-1.cm2.src.rpm
|
||||
tzdata 2022a-1.cm2 1653816591 1653752882 Microsoft Corporation (none) 1535764 noarch 0 tzdata-2022a-1.cm2.src.rpm
|
||||
prebuilt-ca-certificates-base 2.0.0-3.cm2 1653816591 1653771776 Microsoft Corporation (none) 65684 noarch 1 prebuilt-ca-certificates-base-2.0.0-3.cm2.src.rpm
|
||||
distroless-packages-minimal 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
|
||||
distroless-packages-base 0.1-2.cm2 1653816591 1650712132 Microsoft Corporation (none) 0 x86_64 0 distroless-packages-0.1-2.cm2.src.rpm
|
||||
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
|
||||
// Packages is the legacy Berkely db based format
|
||||
@ -20,10 +16,7 @@ const RpmDBGlob = "**/var/lib/rpm/{Packages,Packages.db,rpmdb.sqlite}"
|
||||
// Used in CBL-Mariner distroless images
|
||||
const RpmManifestGlob = "**/var/lib/rpmmanifest/container-manifest-2"
|
||||
|
||||
var (
|
||||
_ FileOwner = (*RpmMetadata)(nil)
|
||||
_ urlIdentifier = (*RpmMetadata)(nil)
|
||||
)
|
||||
var _ FileOwner = (*RpmMetadata)(nil)
|
||||
|
||||
// RpmMetadata represents all captured data for a RPM DB package entry.
|
||||
type RpmMetadata struct {
|
||||
@ -54,40 +47,6 @@ type RpmdbFileRecord struct {
|
||||
// RpmdbFileMode is the raw file mode for a single file. This can be interpreted as the linux stat.h mode (see https://pubs.opengroup.org/onlinepubs/007908799/xsh/sysstat.h.html)
|
||||
type RpmdbFileMode uint16
|
||||
|
||||
// PackageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec)
|
||||
func (m RpmMetadata) PackageURL(distro *linux.Release) string {
|
||||
var namespace string
|
||||
if distro != nil {
|
||||
namespace = distro.ID
|
||||
}
|
||||
|
||||
qualifiers := map[string]string{
|
||||
PURLQualifierArch: m.Arch,
|
||||
}
|
||||
|
||||
if m.Epoch != nil {
|
||||
qualifiers[PURLQualifierEpoch] = strconv.Itoa(*m.Epoch)
|
||||
}
|
||||
|
||||
if m.SourceRpm != "" {
|
||||
qualifiers[PURLQualifierUpstream] = m.SourceRpm
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeRPM,
|
||||
namespace,
|
||||
m.Name,
|
||||
// for purl the epoch is a qualifier, not part of the version
|
||||
// see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst under the RPM section
|
||||
fmt.Sprintf("%s-%s", m.Version, m.Release),
|
||||
PURLQualifiers(
|
||||
qualifiers,
|
||||
distro,
|
||||
),
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
|
||||
func (m RpmMetadata) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
|
||||
@ -5,85 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
)
|
||||
|
||||
func TestRpmMetadata_pURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
distro *linux.Release
|
||||
metadata RpmMetadata
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
distro: &linux.Release{
|
||||
ID: "rhel",
|
||||
VersionID: "8.4",
|
||||
},
|
||||
metadata: RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Release: "r",
|
||||
Epoch: nil,
|
||||
},
|
||||
expected: "pkg:rpm/rhel/p@v-r?distro=rhel-8.4",
|
||||
},
|
||||
{
|
||||
name: "with arch and epoch",
|
||||
distro: &linux.Release{
|
||||
ID: "centos",
|
||||
VersionID: "7",
|
||||
},
|
||||
metadata: RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Arch: "a",
|
||||
Release: "r",
|
||||
Epoch: intRef(1),
|
||||
},
|
||||
expected: "pkg:rpm/centos/p@v-r?arch=a&epoch=1&distro=centos-7",
|
||||
},
|
||||
{
|
||||
name: "missing distro",
|
||||
metadata: RpmMetadata{
|
||||
Name: "p",
|
||||
Version: "v",
|
||||
Release: "r",
|
||||
Epoch: nil,
|
||||
},
|
||||
expected: "pkg:rpm/p@v-r",
|
||||
},
|
||||
{
|
||||
name: "with upstream source rpm info",
|
||||
distro: &linux.Release{
|
||||
ID: "rhel",
|
||||
VersionID: "8.4",
|
||||
},
|
||||
metadata: RpmMetadata{
|
||||
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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := test.metadata.PackageURL(test.distro)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRpmMetadata_FileOwner(t *testing.T) {
|
||||
tests := []struct {
|
||||
metadata RpmMetadata
|
||||
@ -123,7 +46,3 @@ func TestRpmMetadata_FileOwner(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func intRef(i int) *int {
|
||||
return &i
|
||||
}
|
||||
|
||||
@ -35,26 +35,6 @@ func TestPackageURL(t *testing.T) {
|
||||
},
|
||||
expected: "pkg:npm/name@v0.1.0",
|
||||
},
|
||||
{
|
||||
name: "rpm",
|
||||
distro: &linux.Release{
|
||||
ID: "centos",
|
||||
VersionID: "7",
|
||||
},
|
||||
pkg: Package{
|
||||
Name: "bad-name",
|
||||
Version: "bad-v0.1.0",
|
||||
Type: RpmPkg,
|
||||
Metadata: RpmMetadata{
|
||||
Name: "name",
|
||||
Version: "0.1.0",
|
||||
Epoch: intRef(2),
|
||||
Arch: "amd64",
|
||||
Release: "3",
|
||||
},
|
||||
},
|
||||
expected: "pkg:rpm/centos/name@0.1.0-3?arch=amd64&epoch=2&distro=centos-7",
|
||||
},
|
||||
{
|
||||
name: "cargo",
|
||||
pkg: Package{
|
||||
@ -128,6 +108,7 @@ func TestPackageURL(t *testing.T) {
|
||||
expectedTypes.Remove(string(BinaryPkg))
|
||||
expectedTypes.Remove(string(PhpComposerPkg))
|
||||
expectedTypes.Remove(string(PythonPkg))
|
||||
expectedTypes.Remove(string(RpmPkg))
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user