Add abstraction for adding relationships from package cataloger results (#2853)

* add internal dependency resolver

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor dependency relationship resolution to common object

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* replace cataloger decorator with generic processor

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* refactor resolver to be a single function

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for debian

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for arch

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* use common dependency specifier for alpine

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* allow for generic pkg and rel assertions in testpkg helper

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* do not allow for empty results

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* move stable deduplicate comment

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove relationship resolver type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2024-05-14 13:27:36 +00:00
committed by GitHub
parent fae6f5d372
commit 4a18895545
32 changed files with 1207 additions and 801 deletions
+3 -1
View File
@@ -6,10 +6,12 @@ package alpine
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
// NewDBCataloger returns a new cataloger object initialized for Alpine package DB flat-file stores.
func NewDBCataloger() pkg.Cataloger {
return generic.NewCataloger("apk-db-cataloger").
WithParserByGlobs(parseApkDB, pkg.ApkDBGlob)
WithParserByGlobs(parseApkDB, pkg.ApkDBGlob).
WithProcessors(dependency.Processor(dbEntryDependencySpecifier))
}
+230
View File
@@ -3,9 +3,239 @@ package alpine
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"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/internal/pkgtest"
)
func TestApkDBCataloger(t *testing.T) {
dbLocation := file.NewLocation("lib/apk/db/installed")
bashPkg := pkg.Package{
Name: "bash",
Version: "5.2.21-r0",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-3.0-or-later", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
Package: "bash",
OriginPackage: "bash",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
Version: "5.2.21-r0",
Architecture: "x86_64",
URL: "https://www.gnu.org/software/bash/bash.html",
Description: "The GNU Bourne Again shell",
Size: 448728,
InstalledSize: 1396736,
Dependencies: []string{
"/bin/sh", "so:libc.musl-x86_64.so.1", "so:libreadline.so.8",
},
Provides: []string{
"cmd:bash=5.2.21-r0",
},
// note: files not provided and not under test
},
}
busyboxBinshPkg := pkg.Package{
Name: "busybox-binsh",
Version: "1.36.1-r15",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-2.0-only", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
Package: "busybox-binsh",
OriginPackage: "busybox",
Maintainer: "Sören Tempel <soeren+alpine@soeren-tempel.net>",
Version: "1.36.1-r15",
Architecture: "x86_64",
URL: "https://busybox.net/",
Description: "busybox ash /bin/sh",
Size: 1543,
InstalledSize: 8192,
Dependencies: []string{
"busybox=1.36.1-r15",
},
Provides: []string{
"/bin/sh", "cmd:sh=1.36.1-r15",
},
// note: files not provided and not under test
},
}
muslPkg := pkg.Package{
Name: "musl",
Version: "1.2.4_git20230717-r4",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
Package: "musl",
OriginPackage: "musl",
Maintainer: "Timo Teräs <timo.teras@iki.fi>",
Version: "1.2.4_git20230717-r4",
Architecture: "x86_64",
URL: "https://musl.libc.org/",
Description: "the musl c library (libc) implementation",
Size: 407278,
InstalledSize: 667648,
Dependencies: []string{},
Provides: []string{
"so:libc.musl-x86_64.so.1=1",
},
// note: files not provided and not under test
},
}
readlinePkg := pkg.Package{
Name: "readline",
Version: "8.2.1-r2",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-2.0-or-later", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
Package: "readline",
OriginPackage: "readline",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
Version: "8.2.1-r2",
Architecture: "x86_64",
URL: "https://tiswww.cwru.edu/php/chet/readline/rltop.html",
Description: "GNU readline library",
Size: 119878,
InstalledSize: 303104,
Dependencies: []string{
"so:libc.musl-x86_64.so.1", "so:libncursesw.so.6",
},
Provides: []string{
"so:libreadline.so.8=8.2",
},
// note: files not provided and not under test
},
}
expectedPkgs := []pkg.Package{
bashPkg,
busyboxBinshPkg,
muslPkg,
readlinePkg,
}
// # apk info --depends bash
// bash-5.2.21-r0 depends on:
// /bin/sh
// so:libc.musl-x86_64.so.1
// so:libreadline.so.8
//
// # apk info --who-owns /bin/sh
// /bin/sh is owned by busybox-binsh-1.36.1-r15
//
// # find / | grep musl
// /lib/ld-musl-x86_64.so.1
// /lib/libc.musl-x86_64.so.1
//
// # apk info --who-owns '/lib/libc.musl-x86_64.so.1'
// /lib/libc.musl-x86_64.so.1 is owned by musl-1.2.4_git20230717-r4
//
// # find / | grep libreadline
// /usr/lib/libreadline.so.8.2
// /usr/lib/libreadline.so.8
//
// # apk info --who-owns '/usr/lib/libreadline.so.8'
// /usr/lib/libreadline.so.8 is owned by readline-8.2.1-r2
expectedRelationships := []artifact.Relationship{
{
From: busyboxBinshPkg,
To: bashPkg,
Type: artifact.DependencyOfRelationship,
},
{
From: readlinePkg,
To: bashPkg,
Type: artifact.DependencyOfRelationship,
},
{
From: muslPkg,
To: readlinePkg,
Type: artifact.DependencyOfRelationship,
},
{
From: muslPkg,
To: bashPkg,
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/multiple-1").
WithCompareOptions(cmpopts.IgnoreFields(pkg.ApkDBEntry{}, "Files", "GitCommit", "Checksum")).
Expects(expectedPkgs, expectedRelationships).
TestCataloger(t, NewDBCataloger())
}
func TestCatalogerDependencyTree(t *testing.T) {
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
expected := map[string][]string{
"alpine-baselayout": {"busybox", "alpine-baselayout-data", "musl"},
"apk-tools": {"ca-certificates-bundle", "musl", "libcrypto1.1", "libssl1.1", "zlib"},
"busybox": {"musl"},
"libc-utils": {"musl-utils"},
"libcrypto1.1": {"musl"},
"libssl1.1": {"musl", "libcrypto1.1"},
"musl-utils": {"scanelf", "musl"},
"scanelf": {"musl"},
"ssl_client": {"musl", "libcrypto1.1", "libssl1.1"},
"zlib": {"musl"},
}
pkgsByID := make(map[artifact.ID]pkg.Package)
for _, p := range pkgs {
p.SetID()
pkgsByID[p.ID()] = p
}
actualDependencies := make(map[string][]string)
for _, r := range relationships {
switch r.Type {
case artifact.DependencyOfRelationship:
to := pkgsByID[r.To.ID()]
from := pkgsByID[r.From.ID()]
actualDependencies[to.Name] = append(actualDependencies[to.Name], from.Name)
default:
t.Fatalf("unexpected relationship type: %+v", r.Type)
}
}
if d := cmp.Diff(expected, actualDependencies); d != "" {
t.Fail()
t.Log(d)
}
}
pkgtest.NewCatalogTester().
FromDirectory(t, "test-fixtures/multiple-2").
ExpectsAssertion(assertion).
TestCataloger(t, NewDBCataloger())
}
func TestCataloger_Globs(t *testing.T) {
tests := []struct {
name string
+48
View File
@@ -0,0 +1,48 @@
package alpine
import (
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
var _ dependency.Specifier = dbEntryDependencySpecifier
func dbEntryDependencySpecifier(p pkg.Package) dependency.Specification {
meta, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok {
log.Tracef("cataloger failed to extract apk metadata for package %+v", p.Name)
return dependency.Specification{}
}
provides := []string{p.Name}
provides = append(provides, stripVersionSpecifiers(meta.Provides)...)
return dependency.Specification{
Provides: provides,
Requires: stripVersionSpecifiers(meta.Dependencies),
}
}
func stripVersionSpecifiers(given []string) []string {
var keys []string
for _, key := range given {
key = stripVersionSpecifier(key)
if key == "" {
continue
}
keys = append(keys, key)
}
return keys
}
func stripVersionSpecifier(s string) string {
// examples:
// musl>=1 --> musl
// cmd:scanelf=1.3.4-r0 --> cmd:scanelf
return strings.TrimSpace(internal.SplitAny(s, "<>=")[0])
}
@@ -0,0 +1,110 @@
package alpine
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
)
func Test_dbEntryDependencySpecifier(t *testing.T) {
tests := []struct {
name string
p pkg.Package
want dependency.Specification
}{
{
name: "keeps given values + package name",
p: pkg.Package{
Name: "package-c",
Metadata: pkg.ApkDBEntry{
Provides: []string{"a-thing"},
Dependencies: []string{"b-thing"},
},
},
want: dependency.Specification{
Provides: []string{"package-c", "a-thing"},
Requires: []string{"b-thing"},
},
},
{
name: "strip version specifiers",
p: pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Provides: []string{"so:libc.musl-x86_64.so.1=1"},
Dependencies: []string{"so:libc.musl-x86_64.so.2=2"},
},
},
want: dependency.Specification{
Provides: []string{"package-a", "so:libc.musl-x86_64.so.1"},
Requires: []string{"so:libc.musl-x86_64.so.2"},
},
},
{
name: "empty dependency data entries",
p: pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Provides: []string{""},
Dependencies: []string{""},
},
},
want: dependency.Specification{
Provides: []string{"package-a"},
Requires: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, dbEntryDependencySpecifier(tt.p))
})
}
}
func Test_stripVersionSpecifier(t *testing.T) {
tests := []struct {
name string
version string
want string
}{
{
name: "empty expression",
version: "",
want: "",
},
{
name: "no expression",
version: "cmd:foo",
want: "cmd:foo",
},
{
name: "=",
version: "cmd:scanelf=1.3.4-r0",
want: "cmd:scanelf",
},
{
name: ">=",
version: "cmd:scanelf>=1.3.4-r0",
want: "cmd:scanelf",
},
{
name: "<",
version: "cmd:scanelf<1.3.4-r0",
want: "cmd:scanelf",
},
{
name: "ignores file paths",
version: "/bin/sh",
want: "/bin/sh",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, stripVersionSpecifier(tt.version))
})
}
}
+1 -55
View File
@@ -131,7 +131,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
}
return pkgs, discoverPackageDependencies(pkgs), nil
return pkgs, nil, nil
}
func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
@@ -386,57 +386,3 @@ func processChecksum(value string) *file.Digest {
Value: value,
}
}
func discoverPackageDependencies(pkgs []pkg.Package) (relationships []artifact.Relationship) {
// map["provides" string] -> packages that provide the "p" key
lookup := make(map[string][]pkg.Package)
// read "Provides" (p) and add as keys for lookup keys as well as package names
for _, p := range pkgs {
apkg, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok {
log.Warnf("cataloger failed to extract apk 'provides' metadata for package %+v", p.Name)
continue
}
lookup[p.Name] = append(lookup[p.Name], p)
for _, provides := range apkg.Provides {
k := stripVersionSpecifier(provides)
lookup[k] = append(lookup[k], p)
}
}
// read "Pull Dependencies" (D) and match with keys
for _, p := range pkgs {
apkg, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok {
log.Warnf("cataloger failed to extract apk dependency metadata for package %+v", p.Name)
continue
}
for _, depSpecifier := range apkg.Dependencies {
// use the lookup to find what pkg we depend on
dep := stripVersionSpecifier(depSpecifier)
for _, depPkg := range lookup[dep] {
// this is a pkg that package "p" depends on... make a relationship
relationships = append(relationships, artifact.Relationship{
From: depPkg,
To: p,
Type: artifact.DependencyOfRelationship,
})
}
}
}
return relationships
}
func stripVersionSpecifier(s string) string {
// examples:
// musl>=1 --> musl
// cmd:scanelf=1.3.4-r0 --> cmd:scanelf
items := internal.SplitAny(s, "<>=")
if len(items) == 0 {
return s
}
return items[0]
}
@@ -9,11 +9,9 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
@@ -689,144 +687,6 @@ func TestSinglePackageDetails(t *testing.T) {
}
}
func TestMultiplePackages(t *testing.T) {
fixture := "test-fixtures/multiple"
location := file.NewLocation(fixture)
fixtureLocationSet := file.NewLocationSet(location)
expectedPkgs := []pkg.Package{
{
Name: "libc-utils",
Version: "0.7.2-r0",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MPL-2.0 AND MIT", location),
),
Type: pkg.ApkPkg,
PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
Locations: fixtureLocationSet,
Metadata: pkg.ApkDBEntry{
Package: "libc-utils",
OriginPackage: "libc-dev",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
Version: "0.7.2-r0",
Architecture: "x86_64",
URL: "http://alpinelinux.org",
Description: "Meta package to pull in correct libc",
Size: 1175,
InstalledSize: 4096,
Checksum: "Q1p78yvTLG094tHE1+dToJGbmYzQE=",
GitCommit: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479",
Dependencies: []string{"musl-utils"},
Provides: []string{},
Files: []pkg.ApkFileRecord{},
},
},
{
Name: "musl-utils",
Version: "1.1.24-r2",
Type: pkg.ApkPkg,
PURL: "pkg:apk/alpine/musl-utils@1.1.24-r2?arch=x86_64&upstream=musl&distro=alpine-3.12",
Locations: fixtureLocationSet,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", location),
pkg.NewLicenseFromLocations("BSD", location),
pkg.NewLicenseFromLocations("GPL2+", location),
),
Metadata: pkg.ApkDBEntry{
Package: "musl-utils",
OriginPackage: "musl",
Version: "1.1.24-r2",
Description: "the musl c library (libc) implementation",
Maintainer: "Timo Teräs <timo.teras@iki.fi>",
Architecture: "x86_64",
URL: "https://musl.libc.org/",
Size: 37944,
InstalledSize: 151552,
GitCommit: "4024cc3b29ad4c65544ad068b8f59172b5494306",
Dependencies: []string{"scanelf", "so:libc.musl-x86_64.so.1"},
Provides: []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"},
Checksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=",
Files: []pkg.ApkFileRecord{
{
Path: "/sbin",
},
{
Path: "/sbin/ldconfig",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Digest: &file.Digest{
Algorithm: "'Q1'+base64(sha1)",
Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=",
},
},
{
Path: "/usr",
},
{
Path: "/usr/bin",
},
{
Path: "/usr/bin/iconv",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Digest: &file.Digest{
Algorithm: "'Q1'+base64(sha1)",
Value: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=",
},
},
{
Path: "/usr/bin/ldd",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Digest: &file.Digest{
Algorithm: "'Q1'+base64(sha1)",
Value: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=",
},
},
{
Path: "/usr/bin/getconf",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Digest: &file.Digest{
Algorithm: "'Q1'+base64(sha1)",
Value: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=",
},
},
{
Path: "/usr/bin/getent",
OwnerUID: "0",
OwnerGID: "0",
Permissions: "755",
Digest: &file.Digest{
Algorithm: "'Q1'+base64(sha1)",
Value: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=",
},
},
},
},
},
}
expectedRelationships := []artifact.Relationship{
{
From: expectedPkgs[1], // musl-utils
To: expectedPkgs[0], // libc-utils
Type: artifact.DependencyOfRelationship,
Data: nil,
},
}
env := generic.Environment{LinuxRelease: &linux.Release{
ID: "alpine",
VersionID: "3.12",
}}
pkgtest.TestFileParserWithEnv(t, fixture, parseApkDB, &env, expectedPkgs, expectedRelationships)
}
func Test_processChecksum(t *testing.T) {
tests := []struct {
name string
@@ -858,237 +718,6 @@ func Test_processChecksum(t *testing.T) {
}
}
func Test_discoverPackageDependencies(t *testing.T) {
tests := []struct {
name string
genFn func() ([]pkg.Package, []artifact.Relationship)
}{
{
name: "has no dependency",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Provides: []string{"a-thing"},
},
}
a.SetID()
b := pkg.Package{
Name: "package-b",
Metadata: pkg.ApkDBEntry{
Provides: []string{"b-thing"},
},
}
b.SetID()
return []pkg.Package{a, b}, nil
},
},
{
name: "has 1 dependency",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Dependencies: []string{"b-thing"},
},
}
a.SetID()
b := pkg.Package{
Name: "package-b",
Metadata: pkg.ApkDBEntry{
Provides: []string{"b-thing"},
},
}
b.SetID()
return []pkg.Package{a, b}, []artifact.Relationship{
{
From: b,
To: a,
Type: artifact.DependencyOfRelationship,
},
}
},
},
{
name: "strip version specifiers",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Dependencies: []string{"so:libc.musl-x86_64.so.1"},
},
}
a.SetID()
b := pkg.Package{
Name: "package-b",
Metadata: pkg.ApkDBEntry{
Provides: []string{"so:libc.musl-x86_64.so.1=1"},
},
}
b.SetID()
return []pkg.Package{a, b}, []artifact.Relationship{
{
From: b,
To: a,
Type: artifact.DependencyOfRelationship,
},
}
},
},
{
name: "strip version specifiers with empty provides value",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Dependencies: []string{"so:libc.musl-x86_64.so.1"},
},
}
a.SetID()
b := pkg.Package{
Name: "package-b",
Metadata: pkg.ApkDBEntry{
Provides: []string{""},
},
}
b.SetID()
return []pkg.Package{a, b}, nil
},
},
{
name: "depends on package name",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "package-a",
Metadata: pkg.ApkDBEntry{
Dependencies: []string{"musl>=1.2"},
},
}
a.SetID()
b := pkg.Package{
Name: "musl",
Metadata: pkg.ApkDBEntry{
Provides: []string{"so:libc.musl-x86_64.so.1=1"},
},
}
b.SetID()
return []pkg.Package{a, b}, []artifact.Relationship{
{
From: b,
To: a,
Type: artifact.DependencyOfRelationship,
},
}
},
},
{
name: "depends on package file",
genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{
Name: "alpine-baselayout",
Metadata: pkg.ApkDBEntry{
Dependencies: []string{"/bin/sh"},
},
}
a.SetID()
b := pkg.Package{
Name: "busybox",
Metadata: pkg.ApkDBEntry{
Provides: []string{"/bin/sh"},
},
}
b.SetID()
return []pkg.Package{a, b}, []artifact.Relationship{
{
From: b,
To: a,
Type: artifact.DependencyOfRelationship,
},
}
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgs, wantRelationships := test.genFn()
gotRelationships := discoverPackageDependencies(pkgs)
d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{}))
if d != "" {
t.Fail()
t.Log(d)
}
})
}
}
func TestPackageDbDependenciesByParse(t *testing.T) {
tests := []struct {
fixture string
expected map[string][]string
}{
{
fixture: "test-fixtures/installed",
expected: map[string][]string{
"alpine-baselayout": {"alpine-baselayout-data", "busybox", "musl"},
"apk-tools": {"musl", "ca-certificates-bundle", "musl", "libcrypto1.1", "libssl1.1", "zlib"},
"busybox": {"musl"},
"libc-utils": {"musl-utils"},
"libcrypto1.1": {"musl"},
"libssl1.1": {"musl", "libcrypto1.1"},
"musl-utils": {"scanelf", "musl"},
"scanelf": {"musl"},
"ssl_client": {"musl", "libcrypto1.1", "libssl1.1"},
"zlib": {"musl"},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
f, err := os.Open(test.fixture)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, f.Close()) })
pkgs, relationships, err := parseApkDB(context.Background(), nil, nil, file.LocationReadCloser{
Location: file.NewLocation(test.fixture),
ReadCloser: f,
})
require.NoError(t, err)
pkgsByID := make(map[artifact.ID]pkg.Package)
for _, p := range pkgs {
p.SetID()
pkgsByID[p.ID()] = p
}
actualDependencies := make(map[string][]string)
for _, r := range relationships {
switch r.Type {
case artifact.DependencyOfRelationship:
to := pkgsByID[r.To.ID()]
from := pkgsByID[r.From.ID()]
actualDependencies[to.Name] = append(actualDependencies[to.Name], from.Name)
default:
t.Fatalf("unexpected relationship type: %+v", r.Type)
}
}
if d := cmp.Diff(test.expected, actualDependencies); d != "" {
t.Fail()
t.Log(d)
}
})
}
}
func Test_parseApkDB_expectedPkgNames(t *testing.T) {
tests := []struct {
fixture string
@@ -1175,45 +804,6 @@ func newLocationReadCloser(t *testing.T, path string) file.LocationReadCloser {
return file.NewLocationReadCloser(file.NewLocation(path), f)
}
func Test_stripVersionSpecifier(t *testing.T) {
tests := []struct {
name string
version string
want string
}{
{
name: "empty expression",
version: "",
want: "",
},
{
name: "no expression",
version: "cmd:foo",
want: "cmd:foo",
},
{
name: "=",
version: "cmd:scanelf=1.3.4-r0",
want: "cmd:scanelf",
},
{
name: ">=",
version: "cmd:scanelf>=1.3.4-r0",
want: "cmd:scanelf",
},
{
name: "<",
version: "cmd:scanelf<1.3.4-r0",
want: "cmd:scanelf",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, stripVersionSpecifier(tt.version))
})
}
}
func TestParseReleasesFromAPKRepository(t *testing.T) {
tests := []struct {
repos string
@@ -1,56 +0,0 @@
C:Q1p78yvTLG094tHE1+dToJGbmYzQE=
P:libc-utils
V:0.7.2-r0
A:x86_64
S:1175
I:4096
T:Meta package to pull in correct libc
U:http://alpinelinux.org
L:MPL-2.0 AND MIT
o:libc-dev
m:Natanael Copa <ncopa@alpinelinux.org>
t:1575749004
c:97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479
D:musl-utils
C:Q1bTtF5526tETKfL+lnigzIDvm+2o=
P:musl-utils
V:1.1.24-r2
A:x86_64
S:37944
I:151552
T:the musl c library (libc) implementation
U:https://musl.libc.org/
L:MIT BSD GPL2+
o:musl
m:Timo Teräs <timo.teras@iki.fi>
t:1584790550
c:4024cc3b29ad4c65544ad068b8f59172b5494306
D:scanelf so:libc.musl-x86_64.so.1
p:cmd:getconf cmd:getent cmd:iconv cmd:ldconfig cmd:ldd
r:libiconv
F:sbin
R:ldconfig
a:0:0:755
Z:Q1Kja2+POZKxEkUOZqwSjC6kmaED4=
F:usr
F:usr/bin
R:iconv
a:0:0:755
Z:Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=
R:ldd
a:0:0:755
Z:Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=
R:getconf
a:0:0:755
Z:Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=
R:getent
a:0:0:755
Z:Q1eR2Dz/WylabgbWMTkd2+hGmEya4=
@@ -0,0 +1,78 @@
P:bash
V:5.2.21-r0
A:x86_64
S:448728
I:1396736
T:The GNU Bourne Again shell
U:https://www.gnu.org/software/bash/bash.html
L:GPL-3.0-or-later
o:bash
m:Natanael Copa <ncopa@alpinelinux.org>
t:1701073495
c:6a9559d98850225ba80771901ef1abda91cb29aa
D:/bin/sh so:libc.musl-x86_64.so.1 so:libreadline.so.8
p:cmd:bash=5.2.21-r0
P:busybox-binsh
V:1.36.1-r15
A:x86_64
S:1543
I:8192
T:busybox ash /bin/sh
U:https://busybox.net/
L:GPL-2.0-only
o:busybox
m:Sören Tempel <soeren+alpine@soeren-tempel.net>
t:1699383189
c:d1b6f274f29076967826e0ecf6ebcaa5d360272f
k:100
D:busybox=1.36.1-r15
p:/bin/sh cmd:sh=1.36.1-r15
r:busybox-initscripts
F:bin
R:sh
a:0:0:777
Z:Q1pcfTfDNEbNKQc2s1tia7da05M8Q=
P:musl
V:1.2.4_git20230717-r4
A:x86_64
S:407278
I:667648
T:the musl c library (libc) implementation
U:https://musl.libc.org/
L:MIT
o:musl
m:Timo Teräs <timo.teras@iki.fi>
t:1699271358
c:ca7f2ab5e88794e4e654b40776f8a92256f50639
p:so:libc.musl-x86_64.so.1=1
F:lib
R:ld-musl-x86_64.so.1
a:0:0:755
Z:Q1+zEJiG53Cxy7DkV5oZQqeWnzybY=
R:libc.musl-x86_64.so.1
a:0:0:777
Z:Q17yJ3JFNypA4mxhJJr0ou6CzsJVI=
P:readline
V:8.2.1-r2
A:x86_64
S:119878
I:303104
T:GNU readline library
U:https://tiswww.cwru.edu/php/chet/readline/rltop.html
L:GPL-2.0-or-later
o:readline
m:Natanael Copa <ncopa@alpinelinux.org>
t:1684120357
c:33283848034c9885d984c8e8697c645c57324938
D:so:libc.musl-x86_64.so.1 so:libncursesw.so.6
p:so:libreadline.so.8=8.2
F:etc
R:inputrc
Z:Q1ilcgkuEseXEH6iMo9UNjLn1pPfg=
F:usr
F:usr/lib
R:libreadline.so.8
a:0:0:777