From bed986963831a66b7b8259efb6e171bb504047f6 Mon Sep 17 00:00:00 2001 From: Erik Varga Date: Mon, 19 May 2025 02:33:01 -0700 Subject: [PATCH] Move DPKG PURL generation into the PURL gen lib. PiperOrigin-RevId: 760533539 --- binary/proto/proto.go | 4 +- binary/proto/proto_test.go | 15 +- extractor/convert.go | 3 + extractor/convert_test.go | 45 ++ extractor/filesystem/os/dpkg/dpkg.go | 84 +-- extractor/filesystem/os/dpkg/dpkg_test.go | 527 ++++++++++-------- .../os/dpkg/{ => metadata}/metadata.go | 32 +- extractor/filesystem/os/purl/purl.go | 58 ++ extractor/filesystem/os/purl/purl_test.go | 150 +++++ 9 files changed, 615 insertions(+), 303 deletions(-) rename extractor/filesystem/os/dpkg/{ => metadata}/metadata.go (53%) create mode 100644 extractor/filesystem/os/purl/purl.go create mode 100644 extractor/filesystem/os/purl/purl_test.go diff --git a/binary/proto/proto.go b/binary/proto/proto.go index 29837d3d..cc6d421f 100644 --- a/binary/proto/proto.go +++ b/binary/proto/proto.go @@ -42,7 +42,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/misc/vscodeextensions" "github.com/google/osv-scalibr/extractor/filesystem/os/apk" "github.com/google/osv-scalibr/extractor/filesystem/os/cos" - "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" "github.com/google/osv-scalibr/extractor/filesystem/os/flatpak" "github.com/google/osv-scalibr/extractor/filesystem/os/homebrew" "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/module" @@ -278,7 +278,7 @@ func setProtoMetadata(meta any, p *spb.Package) { License: m.License, }, } - case *dpkg.Metadata: + case *dpkgmeta.Metadata: p.Metadata = &spb.Package_DpkgMetadata{ DpkgMetadata: &spb.DPKGPackageMetadata{ PackageName: m.PackageName, diff --git a/binary/proto/proto_test.go b/binary/proto/proto_test.go index 3e9e61d4..191cb49b 100644 --- a/binary/proto/proto_test.go +++ b/binary/proto/proto_test.go @@ -38,6 +38,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements" "github.com/google/osv-scalibr/extractor/filesystem/language/python/wheelegg" "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" "github.com/google/osv-scalibr/extractor/filesystem/os/homebrew" "github.com/google/osv-scalibr/extractor/filesystem/os/nix" "github.com/google/osv-scalibr/extractor/filesystem/os/pacman" @@ -168,9 +169,10 @@ func TestScanResultToProto(t *testing.T) { failure := &plugin.ScanStatus{Status: plugin.ScanStatusFailed, FailureReason: "failure"} failureProto := &spb.ScanStatus{Status: spb.ScanStatus_FAILED, FailureReason: "failure"} purlDPKGPackage := &extractor.Package{ - Name: "software", - Version: "1.0.0", - Metadata: &dpkg.Metadata{ + Name: "software", + Version: "1.0.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "software", PackageVersion: "1.0.0", OSID: "debian", @@ -182,9 +184,10 @@ func TestScanResultToProto(t *testing.T) { Extractor: dpkg.New(dpkg.DefaultConfig()), } purlDPKGAnnotationPackage := &extractor.Package{ - Name: "software", - Version: "1.0.0", - Metadata: &dpkg.Metadata{ + Name: "software", + Version: "1.0.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "software", PackageVersion: "1.0.0", OSID: "debian", diff --git a/extractor/convert.go b/extractor/convert.go index 54451eb7..deb8ff91 100644 --- a/extractor/convert.go +++ b/extractor/convert.go @@ -20,6 +20,7 @@ import ( mavenpurl "github.com/google/osv-scalibr/extractor/filesystem/language/java/purl" npmpurl "github.com/google/osv-scalibr/extractor/filesystem/language/javascript/purl" "github.com/google/osv-scalibr/extractor/filesystem/language/python/pypipurl" + ospurl "github.com/google/osv-scalibr/extractor/filesystem/os/purl" cdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/metadata" cdxpurl "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/purl" spdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx/metadata" @@ -66,6 +67,8 @@ func typeSpecificPURL(p *Package) *purl.PackageURL { return gopurl.MakePackageURL(p.Name, p.Version) case purl.TypeHex: return hexpurl.MakePackageURL(p.Name, p.Version) + case purl.TypeDebian, purl.TypeOpkg: + return ospurl.MakePackageURL(p.Name, p.Version, p.PURLType, p.Metadata) case "windows": return winpurl.MakePackageURL(p.Name, p.Version, p.Metadata) } diff --git a/extractor/convert_test.go b/extractor/convert_test.go index b1b19036..7e326efb 100644 --- a/extractor/convert_test.go +++ b/extractor/convert_test.go @@ -19,6 +19,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/osv-scalibr/extractor" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" cdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/cdx/metadata" spdxmeta "github.com/google/osv-scalibr/extractor/filesystem/sbom/spdx/metadata" "github.com/google/osv-scalibr/purl" @@ -139,6 +140,50 @@ func TestToPURL(t *testing.T) { Version: "1.2.3", }, }, + { + name: "dpkg_purl", + pkg: &extractor.Package{ + Name: "Name", + Version: "1.2.3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ + PackageName: "pkg-name", + OSVersionCodename: "jammy", + }, + Locations: []string{"location"}, + }, + want: &purl.PackageURL{ + Type: purl.TypeDebian, + Namespace: "linux", + Name: "pkg-name", + Version: "1.2.3", + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Distro: "jammy", + }), + }, + }, + { + name: "opkg_purl", + pkg: &extractor.Package{ + Name: "Name", + Version: "1.2.3", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ + PackageName: "pkg-name", + OSVersionCodename: "jammy", + }, + Locations: []string{"location"}, + }, + want: &purl.PackageURL{ + Type: purl.TypeOpkg, + Namespace: "linux", + Name: "pkg-name", + Version: "1.2.3", + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Distro: "jammy", + }), + }, + }, } for _, tt := range tests { diff --git a/extractor/filesystem/os/dpkg/dpkg.go b/extractor/filesystem/os/dpkg/dpkg.go index f54309a5..4c2269c5 100644 --- a/extractor/filesystem/os/dpkg/dpkg.go +++ b/extractor/filesystem/os/dpkg/dpkg.go @@ -28,6 +28,7 @@ import ( "github.com/google/osv-scalibr/extractor" "github.com/google/osv-scalibr/extractor/filesystem" "github.com/google/osv-scalibr/extractor/filesystem/internal/units" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" "github.com/google/osv-scalibr/extractor/filesystem/os/osrelease" "github.com/google/osv-scalibr/inventory" "github.com/google/osv-scalibr/log" @@ -239,10 +240,16 @@ func (e Extractor) extractFromInput(ctx context.Context, input *filesystem.ScanI annotations = append(annotations, extractor.Transitional) } + purlType := purl.TypeDebian + if input.Path == "usr/lib/opkg/status" { + purlType = purl.TypeOpkg + } + p := &extractor.Package{ - Name: pkgName, - Version: pkgVersion, - Metadata: &Metadata{ + Name: pkgName, + Version: pkgVersion, + PURLType: purlType, + Metadata: &dpkgmeta.Metadata{ PackageName: pkgName, PackageVersion: pkgVersion, Status: h.Get("Status"), @@ -260,8 +267,8 @@ func (e Extractor) extractFromInput(ctx context.Context, input *filesystem.ScanI return pkgs, fmt.Errorf("parseSourceNameVersion(%q): %w", h.Get("Source"), err) } if sourceName != "" { - p.Metadata.(*Metadata).SourceName = sourceName - p.Metadata.(*Metadata).SourceVersion = sourceVersion + p.Metadata.(*dpkgmeta.Metadata).SourceName = sourceName + p.Metadata.(*dpkgmeta.Metadata).SourceVersion = sourceVersion } pkgs = append(pkgs, p) @@ -296,75 +303,16 @@ func parseSourceNameVersion(source string) (string, string, error) { return source, "", nil } -func toNamespace(m *Metadata) string { - if m.OSID != "" { - return m.OSID - } - log.Errorf("os-release[ID] not set, fallback to 'linux'") - // TODO(b/298152210): Implement metric - return "linux" -} - -func toDistro(m *Metadata) string { - // e.g. jammy - if m.OSVersionCodename != "" { - return m.OSVersionCodename - } - // fallback: e.g. 22.04 - if m.OSVersionID != "" { - log.Warnf("VERSION_CODENAME not set in os-release, fallback to VERSION_ID") - return m.OSVersionID - } - log.Errorf("VERSION_CODENAME and VERSION_ID not set in os-release") - return "" -} - // ToPURL converts a package created by this extractor into a PURL. +// TODO(b/400910349): Remove and use Package.PURL() directly. func (e Extractor) ToPURL(p *extractor.Package) *purl.PackageURL { - m := p.Metadata.(*Metadata) - q := map[string]string{} - distro := toDistro(m) - if distro != "" { - q[purl.Distro] = distro - } - if m.SourceName != "" { - q[purl.Source] = m.SourceName - } - if m.SourceVersion != "" { - q[purl.SourceVersion] = m.SourceVersion - } - if m.Architecture != "" { - q[purl.Arch] = m.Architecture - } - - // Determine the package type (opkg or dpkg) based on file location - typePurl := "" - - for _, location := range p.Locations { - if location == "usr/lib/opkg/status" { - typePurl = purl.TypeOpkg - break - } - } - - // Default to dpkg if no specific file path matches - if typePurl == "" { - typePurl = purl.TypeDebian - } - - return &purl.PackageURL{ - Type: typePurl, - Name: m.PackageName, - Namespace: toNamespace(m), - Version: p.Version, - Qualifiers: purl.QualifiersFromMap(q), - } + return p.PURL() } // Ecosystem returns the OSV Ecosystem of the software extracted by this extractor. func (Extractor) Ecosystem(p *extractor.Package) string { - m := p.Metadata.(*Metadata) - osID := cases.Title(language.English).String(toNamespace(m)) + m := p.Metadata.(*dpkgmeta.Metadata) + osID := cases.Title(language.English).String(m.ToNamespace()) if m.OSVersionID == "" { return osID } diff --git a/extractor/filesystem/os/dpkg/dpkg_test.go b/extractor/filesystem/os/dpkg/dpkg_test.go index 6811a330..89189a7f 100644 --- a/extractor/filesystem/os/dpkg/dpkg_test.go +++ b/extractor/filesystem/os/dpkg/dpkg_test.go @@ -22,6 +22,7 @@ import ( "os" "path/filepath" "reflect" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -30,6 +31,7 @@ import ( "github.com/google/osv-scalibr/extractor/filesystem" "github.com/google/osv-scalibr/extractor/filesystem/internal/units" "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" scalibrfs "github.com/google/osv-scalibr/fs" "github.com/google/osv-scalibr/inventory" @@ -206,6 +208,7 @@ func TestExtract(t *testing.T) { path string osrelease string cfg dpkg.Config + isOPKG bool wantPackages []*extractor.Package wantErr error wantResultMetric stats.FileExtractedResult @@ -218,9 +221,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "accountsservice", - Version: "22.08.8-6", - Metadata: &dpkg.Metadata{ + Name: "accountsservice", + Version: "22.08.8-6", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "accountsservice", PackageVersion: "22.08.8-6", Status: "install ok installed", @@ -230,12 +234,13 @@ func TestExtract(t *testing.T) { Maintainer: "Debian freedesktop.org maintainers ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -245,12 +250,13 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "adduser", - Version: "3.131", - Metadata: &dpkg.Metadata{ + Name: "adduser", + Version: "3.131", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "adduser", PackageVersion: "3.131", Status: "install ok installed", @@ -260,12 +266,13 @@ func TestExtract(t *testing.T) { Maintainer: "Debian Adduser Developers ", Architecture: "all", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "admin-session", - Version: "2023.06.26.c543406313-00", - Metadata: &dpkg.Metadata{ + Name: "admin-session", + Version: "2023.06.26.c543406313-00", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "admin-session", PackageVersion: "2023.06.26.c543406313-00", Status: "install ok installed", @@ -275,12 +282,13 @@ func TestExtract(t *testing.T) { Maintainer: "nobody@google.com", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "attr", - Version: "1:2.5.1-4", - Metadata: &dpkg.Metadata{ + Name: "attr", + Version: "1:2.5.1-4", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "attr", PackageVersion: "1:2.5.1-4", Status: "install ok installed", @@ -290,13 +298,14 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, // Expect source name. { - Name: "libacl1", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "libacl1", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "libacl1", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -307,13 +316,14 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, // Expect source name and version. { - Name: "util-linux-extra", - Version: "2.38.1-5+b1", - Metadata: &dpkg.Metadata{ + Name: "util-linux-extra", + Version: "2.38.1-5+b1", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "util-linux-extra", PackageVersion: "2.38.1-5+b1", Status: "install ok installed", @@ -325,7 +335,7 @@ func TestExtract(t *testing.T) { Maintainer: "util-linux packagers ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/valid"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -336,9 +346,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "foo", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "foo", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "foo", PackageVersion: "1.0", Status: "install ok installed", @@ -346,12 +357,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/noversion"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "bar", - Version: "2.0", - Metadata: &dpkg.Metadata{ + Name: "bar", + Version: "2.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "bar", PackageVersion: "2.0", Status: "install ok installed", @@ -359,7 +371,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/noversion"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -371,9 +383,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "foo", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "foo", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "foo", PackageVersion: "1.0", Status: "install ok installed", @@ -381,12 +394,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/nopackage"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "bar", - Version: "2.0", - Metadata: &dpkg.Metadata{ + Name: "bar", + Version: "2.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "bar", PackageVersion: "2.0", Status: "install ok installed", @@ -394,7 +408,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/nopackage"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -406,9 +420,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "wantinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_installed", PackageVersion: "1.0", Status: "install ok installed", @@ -416,12 +431,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantdeinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantdeinstall_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantdeinstall_installed", PackageVersion: "1.0", Status: "deinstall reinstreq installed", @@ -429,12 +445,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantpurge_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantpurge_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantpurge_installed", PackageVersion: "1.0", Status: "purge ok installed", @@ -442,7 +459,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -457,9 +474,10 @@ func TestExtract(t *testing.T) { }, wantPackages: []*extractor.Package{ { - Name: "wantinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_installed", PackageVersion: "1.0", Status: "install ok installed", @@ -467,12 +485,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantdeinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantdeinstall_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantdeinstall_installed", PackageVersion: "1.0", Status: "deinstall reinstreq installed", @@ -480,12 +499,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantdeinstall_configfiles", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantdeinstall_configfiles", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantdeinstall_configfiles", PackageVersion: "1.0", Status: "deinstall ok config-files", @@ -493,12 +513,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantinstall_unpacked", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_unpacked", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_unpacked", PackageVersion: "1.0", Status: "install ok unpacked", @@ -506,12 +527,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantpurge_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantpurge_installed", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantpurge_installed", PackageVersion: "1.0", Status: "purge ok installed", @@ -519,12 +541,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantinstall_halfinstalled", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_halfinstalled", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_halfinstalled", PackageVersion: "1.0", Status: "install reinstreq half-installed", @@ -532,19 +555,20 @@ func TestExtract(t *testing.T) { OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, { - Name: "wantnostatus", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantnostatus", + Version: "1.0", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantnostatus", PackageVersion: "1.0", OSID: "debian", OSVersionCodename: "bookworm", OSVersionID: "12", }, - Locations: []string{"testdata/dpkg/statusfield"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -571,9 +595,10 @@ func TestExtract(t *testing.T) { ID=debian`, wantPackages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -582,7 +607,7 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/single"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -593,9 +618,10 @@ func TestExtract(t *testing.T) { osrelease: `ID=debian`, wantPackages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -603,7 +629,7 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/single"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -614,9 +640,10 @@ func TestExtract(t *testing.T) { osrelease: "VERSION_CODENAME=bookworm", wantPackages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -624,7 +651,7 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/single"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -638,9 +665,10 @@ func TestExtract(t *testing.T) { ID_LIKE=debian`, wantPackages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -650,7 +678,7 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/single"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -664,9 +692,10 @@ func TestExtract(t *testing.T) { ID_LIKE=debian`, wantPackages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -676,7 +705,7 @@ func TestExtract(t *testing.T) { Maintainer: "Guillem Jover ", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/trailingnewlines"}, + Locations: []string{"var/lib/dpkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -689,9 +718,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "foo", - Version: "1.2.3", - Metadata: &dpkg.Metadata{ + Name: "foo", + Version: "1.2.3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "foo", PackageVersion: "1.2.3", OSID: "debian", @@ -700,7 +730,7 @@ func TestExtract(t *testing.T) { Maintainer: "someone", Architecture: "amd64", }, - Locations: []string{"testdata/dpkg/status.d/foo"}, + Locations: []string{"var/lib/dpkg/status.d/foo"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -719,9 +749,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "iceweasel", - Version: "78.13.0esr-1~deb10u1", - Metadata: &dpkg.Metadata{ + Name: "iceweasel", + Version: "78.13.0esr-1~deb10u1", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "iceweasel", Status: "install ok installed", PackageVersion: "78.13.0esr-1~deb10u1", @@ -732,7 +763,7 @@ func TestExtract(t *testing.T) { Maintainer: "Maintainers of Mozilla-related packages ", Architecture: "all", }, - Locations: []string{"testdata/dpkg/transitional"}, + Locations: []string{"var/lib/dpkg/status"}, Annotations: []extractor.Annotation{extractor.Transitional}, }, }, @@ -744,9 +775,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "git-core", - Version: "1:2.14.2-1", - Metadata: &dpkg.Metadata{ + Name: "git-core", + Version: "1:2.14.2-1", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "git-core", Status: "install ok installed", PackageVersion: "1:2.14.2-1", @@ -757,7 +789,7 @@ func TestExtract(t *testing.T) { Maintainer: "Gerrit Pape ", Architecture: "all", }, - Locations: []string{"testdata/dpkg/transitional_dummy"}, + Locations: []string{"var/lib/dpkg/status"}, Annotations: []extractor.Annotation{extractor.Transitional}, }, }, @@ -769,9 +801,10 @@ func TestExtract(t *testing.T) { osrelease: DebianBookworm, wantPackages: []*extractor.Package{ { - Name: "runit-systemd", - Version: "2.1.2-54+usrmerge", - Metadata: &dpkg.Metadata{ + Name: "runit-systemd", + Version: "2.1.2-54+usrmerge", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "runit-systemd", Status: "install ok installed", PackageVersion: "2.1.2-54+usrmerge", @@ -782,7 +815,7 @@ func TestExtract(t *testing.T) { Maintainer: "Lorenzo Puliti ", Architecture: "all", }, - Locations: []string{"testdata/dpkg/transitional_empty"}, + Locations: []string{"var/lib/dpkg/status"}, Annotations: []extractor.Annotation{extractor.Transitional}, }, }, @@ -792,11 +825,13 @@ func TestExtract(t *testing.T) { name: "valid opkg status file", path: "testdata/opkg/valid", // Path to your OPKG status file in the test data osrelease: OpkgRelease, // You can mock the os-release data as needed + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -805,12 +840,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/valid"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "libuci20130104", - Version: "2023.08.10~5781664d-r1", - Metadata: &dpkg.Metadata{ + Name: "libuci20130104", + Version: "2023.08.10~5781664d-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "libuci20130104", PackageVersion: "2023.08.10~5781664d-r1", Status: "install ok installed", @@ -819,12 +855,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/valid"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "busybox", - Version: "1.36.1-r2", - Metadata: &dpkg.Metadata{ + Name: "busybox", + Version: "1.36.1-r2", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "busybox", PackageVersion: "1.36.1-r2", Status: "install ok installed", @@ -833,7 +870,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/valid"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -842,11 +879,13 @@ func TestExtract(t *testing.T) { name: "packages with no version set are skipped", path: "testdata/opkg/noversion", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -855,12 +894,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/noversion"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "busybox", - Version: "1.36.1-r2", - Metadata: &dpkg.Metadata{ + Name: "busybox", + Version: "1.36.1-r2", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "busybox", PackageVersion: "1.36.1-r2", Status: "install ok installed", @@ -869,7 +909,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/noversion"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -879,11 +919,13 @@ func TestExtract(t *testing.T) { name: "packages with no name set are skipped", path: "testdata/opkg/nopackage", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -892,12 +934,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/nopackage"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "busybox", - Version: "1.36.1-r2", - Metadata: &dpkg.Metadata{ + Name: "busybox", + Version: "1.36.1-r2", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "busybox", PackageVersion: "1.36.1-r2", Status: "install ok installed", @@ -906,7 +949,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/nopackage"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -916,11 +959,13 @@ func TestExtract(t *testing.T) { name: "statusfield", path: "testdata/opkg/statusfield", // Path to your OPKG status file in the test data osrelease: OpkgRelease, // You can mock the os-release data as needed + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "wantinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_installed", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_installed", PackageVersion: "1.0", Status: "install ok installed", @@ -928,12 +973,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantpurge_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantpurge_installed", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantpurge_installed", PackageVersion: "1.0", Status: "purge ok installed", @@ -941,7 +987,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -951,14 +997,16 @@ func TestExtract(t *testing.T) { name: "statusfield including not installed", path: "testdata/opkg/statusfield", // Path to your OPKG status file in the test data osrelease: OpkgRelease, // You can mock the os-release data as needed + isOPKG: true, cfg: dpkg.Config{ IncludeNotInstalled: true, }, wantPackages: []*extractor.Package{ { - Name: "wantinstall_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_installed", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_installed", PackageVersion: "1.0", Status: "install ok installed", @@ -966,12 +1014,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantdeinstall_configfiles", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantdeinstall_configfiles", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantdeinstall_configfiles", PackageVersion: "1.0", Status: "deinstall ok config-files", @@ -979,12 +1028,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantinstall_unpacked", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantinstall_unpacked", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantinstall_unpacked", PackageVersion: "1.0", Status: "install ok unpacked", @@ -992,12 +1042,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantpurge_installed", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantpurge_installed", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantpurge_installed", PackageVersion: "1.0", Status: "purge ok installed", @@ -1005,12 +1056,13 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantpurge_notinstalled", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantpurge_notinstalled", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantpurge_notinstalled", PackageVersion: "1.0", Status: "purge ok not-installed", @@ -1018,19 +1070,20 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, { - Name: "wantnostatus", - Version: "1.0", - Metadata: &dpkg.Metadata{ + Name: "wantnostatus", + Version: "1.0", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "wantnostatus", PackageVersion: "1.0", OSID: "openwrt", OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/statusfield"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -1039,6 +1092,7 @@ func TestExtract(t *testing.T) { name: "empty", path: "testdata/opkg/empty", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{}, wantResultMetric: stats.FileExtractedResultSuccess, }, @@ -1046,6 +1100,7 @@ func TestExtract(t *testing.T) { name: "invalid", path: "testdata/opkg/invalid", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{}, wantErr: cmpopts.AnyError, wantResultMetric: stats.FileExtractedResultErrorUnknown, @@ -1055,11 +1110,13 @@ func TestExtract(t *testing.T) { path: "testdata/opkg/single", osrelease: `VERSION_ID="21.02.1" ID=openwrt`, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -1067,7 +1124,7 @@ func TestExtract(t *testing.T) { OSID: "openwrt", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/single"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -1076,18 +1133,20 @@ func TestExtract(t *testing.T) { name: "no version", path: "testdata/opkg/single", osrelease: `ID=openwrt`, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", Architecture: "x86_64", OSID: "openwrt", }, - Locations: []string{"testdata/opkg/single"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -1096,18 +1155,20 @@ func TestExtract(t *testing.T) { name: "osrelease id not set", path: "testdata/opkg/single", osrelease: `VERSION_CODENAME=openwrt-21.02.1`, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", Architecture: "x86_64", OSVersionCodename: "openwrt-21.02.1", }, - Locations: []string{"testdata/opkg/single"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -1116,11 +1177,13 @@ func TestExtract(t *testing.T) { name: "newlines", path: "testdata/opkg/trailingnewlines", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -1129,7 +1192,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/trailingnewlines"}, + Locations: []string{"usr/lib/opkg/status"}, }, }, wantResultMetric: stats.FileExtractedResultSuccess, @@ -1140,11 +1203,13 @@ func TestExtract(t *testing.T) { name: "transitional packages should be annotated", path: "testdata/opkg/transitional", osrelease: OpkgRelease, + isOPKG: true, wantPackages: []*extractor.Package{ { - Name: "ubus", - Version: "2024.10.20~252a9b0c-r1", - Metadata: &dpkg.Metadata{ + Name: "ubus", + Version: "2024.10.20~252a9b0c-r1", + PURLType: purl.TypeOpkg, + Metadata: &dpkgmeta.Metadata{ PackageName: "ubus", PackageVersion: "2024.10.20~252a9b0c-r1", Status: "install ok installed", @@ -1153,7 +1218,7 @@ func TestExtract(t *testing.T) { OSVersionCodename: "openwrt-21.02.1", OSVersionID: "21.02.1", }, - Locations: []string{"testdata/opkg/transitional"}, + Locations: []string{"usr/lib/opkg/status"}, Annotations: []extractor.Annotation{extractor.Transitional}, }, }, @@ -1188,6 +1253,15 @@ func TestExtract(t *testing.T) { t.Fatalf("Failed to stat test file: %v", err) } + // Use valid os package descriptor paths instead of the testdata paths since the Extractor + // uses the path to differentiate between things like PURL types. + if tt.isOPKG { + tt.path = "usr/lib/opkg/status" + } else if strings.Contains(tt.path, "status.d") { + tt.path = "var/lib/dpkg/status.d" + strings.Split(tt.path, "status.d")[1] + } else { + tt.path = "var/lib/dpkg/status" + } input := &filesystem.ScanInput{ FS: scalibrfs.DirFS(d), Path: tt.path, Reader: r, Root: d, Info: info, } @@ -1287,9 +1361,10 @@ func TestExtractNonexistentOSRelease(t *testing.T) { want := inventory.Inventory{Packages: []*extractor.Package{ { - Name: "acl", - Version: "2.3.1-3", - Metadata: &dpkg.Metadata{ + Name: "acl", + Version: "2.3.1-3", + PURLType: purl.TypeDebian, + Metadata: &dpkgmeta.Metadata{ PackageName: "acl", PackageVersion: "2.3.1-3", Status: "install ok installed", @@ -1338,14 +1413,14 @@ func TestToPURL(t *testing.T) { e := dpkg.Extractor{} tests := []struct { name string - location string - metadata *dpkg.Metadata + purlType string + metadata *dpkgmeta.Metadata want *purl.PackageURL }{ { name: "both OS versions present", - location: "var/lib/dpkg/status", - metadata: &dpkg.Metadata{ + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ PackageName: pkgname, SourceName: sourcename, SourceVersion: sourceversion, @@ -1367,8 +1442,8 @@ func TestToPURL(t *testing.T) { }, { name: "only VERSION_ID set", - location: "var/lib/dpkg/status", - metadata: &dpkg.Metadata{ + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ PackageName: pkgname, SourceName: sourcename, SourceVersion: sourceversion, @@ -1389,8 +1464,8 @@ func TestToPURL(t *testing.T) { }, { name: "ID not set, fallback to linux", - location: "var/lib/dpkg/status", - metadata: &dpkg.Metadata{ + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ PackageName: pkgname, SourceName: sourcename, SourceVersion: sourceversion, @@ -1411,8 +1486,8 @@ func TestToPURL(t *testing.T) { }, { name: "OS ID and OS Version set (OpenWrt)", - location: "usr/lib/opkg/status", - metadata: &dpkg.Metadata{ + purlType: purl.TypeOpkg, + metadata: &dpkgmeta.Metadata{ PackageName: pkgname, OSID: "openwrt", OSVersionID: "22.03.5", @@ -1429,8 +1504,8 @@ func TestToPURL(t *testing.T) { }, { name: "OS ID not set, fallback to linux", - location: "usr/lib/opkg/status", - metadata: &dpkg.Metadata{ + purlType: purl.TypeOpkg, + metadata: &dpkgmeta.Metadata{ PackageName: pkgname, OSVersionCodename: "jammy", OSVersionID: "5.10", @@ -1449,10 +1524,10 @@ func TestToPURL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &extractor.Package{ - Name: pkgname, - Version: version, - Metadata: tt.metadata, - Locations: []string{tt.location}, + Name: pkgname, + Version: version, + PURLType: tt.purlType, + Metadata: tt.metadata, } got := e.ToPURL(p) if diff := cmp.Diff(tt.want, got); diff != "" { @@ -1466,24 +1541,24 @@ func TestEcosystem(t *testing.T) { e := dpkg.Extractor{} tests := []struct { name string - metadata *dpkg.Metadata + metadata *dpkgmeta.Metadata want string }{ { name: "OS ID present", - metadata: &dpkg.Metadata{ + metadata: &dpkgmeta.Metadata{ OSID: "debian", }, want: "Debian", }, { name: "OS ID not present", - metadata: &dpkg.Metadata{}, + metadata: &dpkgmeta.Metadata{}, want: "Linux", }, { name: "OS version present", - metadata: &dpkg.Metadata{ + metadata: &dpkgmeta.Metadata{ OSID: "debian", OSVersionID: "12", }, @@ -1491,14 +1566,14 @@ func TestEcosystem(t *testing.T) { }, { name: "OS ID present (OpenWrt)", - metadata: &dpkg.Metadata{ + metadata: &dpkgmeta.Metadata{ OSID: "openwrt", }, want: "Openwrt", }, { name: "OS version present (OpenWrt)", - metadata: &dpkg.Metadata{ + metadata: &dpkgmeta.Metadata{ OSID: "openwrt", OSVersionID: "22.03.5", }, @@ -1506,7 +1581,7 @@ func TestEcosystem(t *testing.T) { }, { name: "OS version present (Generic Linux)", - metadata: &dpkg.Metadata{ + metadata: &dpkgmeta.Metadata{ OSID: "linux", OSVersionID: "5", }, diff --git a/extractor/filesystem/os/dpkg/metadata.go b/extractor/filesystem/os/dpkg/metadata/metadata.go similarity index 53% rename from extractor/filesystem/os/dpkg/metadata.go rename to extractor/filesystem/os/dpkg/metadata/metadata.go index d3c5c124..282df0ee 100644 --- a/extractor/filesystem/os/dpkg/metadata.go +++ b/extractor/filesystem/os/dpkg/metadata/metadata.go @@ -12,7 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dpkg +// Package metadata defined a Metadata struct for DPKG packages. +package metadata + +import ( + "github.com/google/osv-scalibr/log" +) // Metadata holds parsing information for a dpkg package. type Metadata struct { @@ -27,3 +32,28 @@ type Metadata struct { Maintainer string Architecture string } + +// ToNamespace extracts the PURL namespace from the metadata. +func (m *Metadata) ToNamespace() string { + if m.OSID != "" { + return m.OSID + } + log.Errorf("os-release[ID] not set, fallback to 'linux'") + // TODO(b/298152210): Implement metric + return "linux" +} + +// ToDistro extracts the OS distro from the metadata. +func (m *Metadata) ToDistro() string { + // e.g. jammy + if m.OSVersionCodename != "" { + return m.OSVersionCodename + } + // fallback: e.g. 22.04 + if m.OSVersionID != "" { + log.Warnf("VERSION_CODENAME not set in os-release, fallback to VERSION_ID") + return m.OSVersionID + } + log.Errorf("VERSION_CODENAME and VERSION_ID not set in os-release") + return "" +} diff --git a/extractor/filesystem/os/purl/purl.go b/extractor/filesystem/os/purl/purl.go new file mode 100644 index 00000000..af93f0f9 --- /dev/null +++ b/extractor/filesystem/os/purl/purl.go @@ -0,0 +1,58 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package purl converts OS package details into PackageURLs. +package purl + +import ( + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" + "github.com/google/osv-scalibr/purl" +) + +// MakePackageURL returns a package URL that follows the specific OS's spec +// and includes OS version info. +func MakePackageURL(_name string, version string, purlType string, metadata any) *purl.PackageURL { + q := map[string]string{} + var name string + var namespace string + switch m := metadata.(type) { + case *dpkgmeta.Metadata: + name = m.PackageName + namespace = m.ToNamespace() + + distro := m.ToDistro() + if distro != "" { + q[purl.Distro] = distro + } + if m.SourceName != "" { + q[purl.Source] = m.SourceName + } + if m.SourceVersion != "" { + q[purl.SourceVersion] = m.SourceVersion + } + if m.Architecture != "" { + q[purl.Arch] = m.Architecture + } + default: + return nil + } + + return &purl.PackageURL{ + Type: purlType, + Name: name, + Namespace: namespace, + Version: version, + Qualifiers: purl.QualifiersFromMap(q), + } +} diff --git a/extractor/filesystem/os/purl/purl_test.go b/extractor/filesystem/os/purl/purl_test.go new file mode 100644 index 00000000..10f91952 --- /dev/null +++ b/extractor/filesystem/os/purl/purl_test.go @@ -0,0 +1,150 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package purl_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + dpkgmeta "github.com/google/osv-scalibr/extractor/filesystem/os/dpkg/metadata" + ospurl "github.com/google/osv-scalibr/extractor/filesystem/os/purl" + "github.com/google/osv-scalibr/purl" +) + +func TestMakePackageURLDebian(t *testing.T) { + pkgname := "pkgname" + sourcename := "sourcename" + version := "1.2.3" + sourceversion := "1.2.4" + source := "sourcename" + tests := []struct { + name string + purlType string + metadata *dpkgmeta.Metadata + want *purl.PackageURL + }{ + { + name: "both OS versions present", + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ + PackageName: pkgname, + SourceName: sourcename, + SourceVersion: sourceversion, + OSID: "debian", + OSVersionCodename: "jammy", + OSVersionID: "22.04", + }, + want: &purl.PackageURL{ + Type: purl.TypeDebian, + Name: pkgname, + Namespace: "debian", + Version: version, + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Source: source, + purl.SourceVersion: sourceversion, + purl.Distro: "jammy", + }), + }, + }, + { + name: "only VERSION_ID set", + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ + PackageName: pkgname, + SourceName: sourcename, + SourceVersion: sourceversion, + OSID: "debian", + OSVersionID: "22.04", + }, + want: &purl.PackageURL{ + Type: purl.TypeDebian, + Name: pkgname, + Namespace: "debian", + Version: version, + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Source: source, + purl.SourceVersion: sourceversion, + purl.Distro: "22.04", + }), + }, + }, + { + name: "ID not set, fallback to linux", + purlType: purl.TypeDebian, + metadata: &dpkgmeta.Metadata{ + PackageName: pkgname, + SourceName: sourcename, + SourceVersion: sourceversion, + OSVersionCodename: "jammy", + OSVersionID: "22.04", + }, + want: &purl.PackageURL{ + Type: purl.TypeDebian, + Name: pkgname, + Namespace: "linux", + Version: version, + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Source: source, + purl.SourceVersion: sourceversion, + purl.Distro: "jammy", + }), + }, + }, + { + name: "OS ID and OS Version set (OpenWrt)", + purlType: purl.TypeOpkg, + metadata: &dpkgmeta.Metadata{ + PackageName: pkgname, + OSID: "openwrt", + OSVersionID: "22.03.5", + }, + want: &purl.PackageURL{ + Type: purl.TypeOpkg, + Name: pkgname, + Namespace: "openwrt", + Version: version, + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Distro: "22.03.5", + }), + }, + }, + { + name: "OS ID not set, fallback to linux", + purlType: purl.TypeOpkg, + metadata: &dpkgmeta.Metadata{ + PackageName: pkgname, + OSVersionCodename: "jammy", + OSVersionID: "5.10", + }, + want: &purl.PackageURL{ + Type: purl.TypeOpkg, + Name: pkgname, + Namespace: "linux", + Version: version, + Qualifiers: purl.QualifiersFromMap(map[string]string{ + purl.Distro: "jammy", + }), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ospurl.MakePackageURL(tt.name, version, tt.purlType, tt.metadata) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("ospurl.MakePackageURL(%v, %v, %v, %v): unexpected PURL (-want +got):\n%s", tt.name, version, tt.purlType, tt.metadata, diff) + } + }) + } +}