From 3048382bbdd7ab85679199dba233baa17f2d7a1a Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 4 Nov 2022 10:41:04 -0400 Subject: [PATCH] port rpm cataloger to new generic cataloger pattern (#1321) Signed-off-by: Alex Goodman Signed-off-by: Alex Goodman --- syft/pkg/cataloger/cataloger.go | 6 +- syft/pkg/cataloger/rpm/cataloger.go | 22 +++ syft/pkg/cataloger/rpm/db_cataloger.go | 75 ---------- syft/pkg/cataloger/rpm/file_cataloger.go | 140 ------------------ syft/pkg/cataloger/rpm/package.go | 122 +++++++++++++++ syft/pkg/cataloger/rpm/package_test.go | 84 +++++++++++ syft/pkg/cataloger/rpm/parse_rpm.go | 95 ++++++++++++ .../rpm/{parse_rpmdb.go => parse_rpm_db.go} | 68 ++++----- ...rse_rpmdb_test.go => parse_rpm_db_test.go} | 74 +++++---- ...e_rpmmanifest.go => parse_rpm_manifest.go} | 87 ++++------- ...est_test.go => parse_rpm_manifest_test.go} | 56 ++----- ...le_cataloger_test.go => parse_rpm_test.go} | 44 ++---- .../rpm/test-fixtures/container-manifest-2 | 9 +- syft/pkg/rpm_metadata.go | 43 +----- syft/pkg/rpm_metadata_test.go | 81 ---------- syft/pkg/url_test.go | 21 +-- 16 files changed, 444 insertions(+), 583 deletions(-) create mode 100644 syft/pkg/cataloger/rpm/cataloger.go delete mode 100644 syft/pkg/cataloger/rpm/db_cataloger.go delete mode 100644 syft/pkg/cataloger/rpm/file_cataloger.go create mode 100644 syft/pkg/cataloger/rpm/package.go create mode 100644 syft/pkg/cataloger/rpm/package_test.go create mode 100644 syft/pkg/cataloger/rpm/parse_rpm.go rename syft/pkg/cataloger/rpm/{parse_rpmdb.go => parse_rpm_db.go} (64%) rename syft/pkg/cataloger/rpm/{parse_rpmdb_test.go => parse_rpm_db_test.go} (76%) rename syft/pkg/cataloger/rpm/{parse_rpmmanifest.go => parse_rpm_manifest.go} (50%) rename syft/pkg/cataloger/rpm/{parse_rpmmanifest_test.go => parse_rpm_manifest_test.go} (67%) rename syft/pkg/cataloger/rpm/{file_cataloger_test.go => parse_rpm_test.go} (82%) diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index 452b326eb..f873d9fbf 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -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(), diff --git a/syft/pkg/cataloger/rpm/cataloger.go b/syft/pkg/cataloger/rpm/cataloger.go new file mode 100644 index 000000000..eecebdc97 --- /dev/null +++ b/syft/pkg/cataloger/rpm/cataloger.go @@ -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") +} diff --git a/syft/pkg/cataloger/rpm/db_cataloger.go b/syft/pkg/cataloger/rpm/db_cataloger.go deleted file mode 100644 index 67169c4d8..000000000 --- a/syft/pkg/cataloger/rpm/db_cataloger.go +++ /dev/null @@ -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 -} diff --git a/syft/pkg/cataloger/rpm/file_cataloger.go b/syft/pkg/cataloger/rpm/file_cataloger.go deleted file mode 100644 index 693349ca6..000000000 --- a/syft/pkg/cataloger/rpm/file_cataloger.go +++ /dev/null @@ -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 -} diff --git a/syft/pkg/cataloger/rpm/package.go b/syft/pkg/cataloger/rpm/package.go new file mode 100644 index 000000000..cf277e780 --- /dev/null +++ b/syft/pkg/cataloger/rpm/package.go @@ -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() +} diff --git a/syft/pkg/cataloger/rpm/package_test.go b/syft/pkg/cataloger/rpm/package_test.go new file mode 100644 index 000000000..9468928b4 --- /dev/null +++ b/syft/pkg/cataloger/rpm/package_test.go @@ -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)) + } + }) + } +} diff --git a/syft/pkg/cataloger/rpm/parse_rpm.go b/syft/pkg/cataloger/rpm/parse_rpm.go new file mode 100644 index 000000000..4f64921f9 --- /dev/null +++ b/syft/pkg/cataloger/rpm/parse_rpm.go @@ -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 +} diff --git a/syft/pkg/cataloger/rpm/parse_rpmdb.go b/syft/pkg/cataloger/rpm/parse_rpm_db.go similarity index 64% rename from syft/pkg/cataloger/rpm/parse_rpmdb.go rename to syft/pkg/cataloger/rpm/parse_rpm_db.go index 96ffd08fb..58e96e9cb 100644 --- a/syft/pkg/cataloger/rpm/parse_rpmdb.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_db.go @@ -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() diff --git a/syft/pkg/cataloger/rpm/parse_rpmdb_test.go b/syft/pkg/cataloger/rpm/parse_rpm_db_test.go similarity index 76% rename from syft/pkg/cataloger/rpm/parse_rpmdb_test.go rename to syft/pkg/cataloger/rpm/parse_rpm_db_test.go index 8b8571120..8ca68434a 100644 --- a/syft/pkg/cataloger/rpm/parse_rpmdb_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_db_test.go @@ -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) }) } diff --git a/syft/pkg/cataloger/rpm/parse_rpmmanifest.go b/syft/pkg/cataloger/rpm/parse_rpm_manifest.go similarity index 50% rename from syft/pkg/cataloger/rpm/parse_rpmmanifest.go rename to syft/pkg/cataloger/rpm/parse_rpm_manifest.go index e71dce90e..2b6d6c733 100644 --- a/syft/pkg/cataloger/rpm/parse_rpmmanifest.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_manifest.go @@ -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 } diff --git a/syft/pkg/cataloger/rpm/parse_rpmmanifest_test.go b/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go similarity index 67% rename from syft/pkg/cataloger/rpm/parse_rpmmanifest_test.go rename to syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go index 977a1077d..64cca390a 100644 --- a/syft/pkg/cataloger/rpm/parse_rpmmanifest_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go @@ -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) - } - } - } } diff --git a/syft/pkg/cataloger/rpm/file_cataloger_test.go b/syft/pkg/cataloger/rpm/parse_rpm_test.go similarity index 82% rename from syft/pkg/cataloger/rpm/file_cataloger_test.go rename to syft/pkg/cataloger/rpm/parse_rpm_test.go index a576d2956..1d082e693 100644 --- a/syft/pkg/cataloger/rpm/file_cataloger_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_test.go @@ -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()) }) } } diff --git a/syft/pkg/cataloger/rpm/test-fixtures/container-manifest-2 b/syft/pkg/cataloger/rpm/test-fixtures/container-manifest-2 index 576e121ac..1b934cd2c 100644 --- a/syft/pkg/cataloger/rpm/test-fixtures/container-manifest-2 +++ b/syft/pkg/cataloger/rpm/test-fixtures/container-manifest-2 @@ -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 + diff --git a/syft/pkg/rpm_metadata.go b/syft/pkg/rpm_metadata.go index 5e5ed6294..1491b4900 100644 --- a/syft/pkg/rpm_metadata.go +++ b/syft/pkg/rpm_metadata.go @@ -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 { diff --git a/syft/pkg/rpm_metadata_test.go b/syft/pkg/rpm_metadata_test.go index 84ddef60d..02dd9bfc7 100644 --- a/syft/pkg/rpm_metadata_test.go +++ b/syft/pkg/rpm_metadata_test.go @@ -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 -} diff --git a/syft/pkg/url_test.go b/syft/pkg/url_test.go index e06169820..eb89abbd6 100644 --- a/syft/pkg/url_test.go +++ b/syft/pkg/url_test.go @@ -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) {