mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
507 lines
14 KiB
Go
507 lines
14 KiB
Go
package syft
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/scylladb/go-set/strset"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/internal/task"
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/cataloging"
|
|
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
|
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
|
"github.com/anchore/syft/syft/file"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
"github.com/anchore/syft/syft/source"
|
|
)
|
|
|
|
var _ pkg.Cataloger = (*dummyCataloger)(nil)
|
|
|
|
type dummyCataloger struct {
|
|
name string
|
|
}
|
|
|
|
func newDummyCataloger(name string) pkg.Cataloger {
|
|
return dummyCataloger{name: name}
|
|
}
|
|
|
|
func (d dummyCataloger) Name() string {
|
|
return d.name
|
|
}
|
|
|
|
func (d dummyCataloger) Catalog(_ context.Context, _ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|
pkgIntersect := func(intersect ...string) []string {
|
|
var sets []*strset.Set
|
|
for _, s := range intersect {
|
|
sets = append(sets, strset.New(pkgCatalogerNamesWithTagOrName(t, s)...))
|
|
}
|
|
|
|
intersectSet := strset.Intersection(sets...)
|
|
|
|
slice := intersectSet.List()
|
|
|
|
sort.Strings(slice)
|
|
|
|
return slice
|
|
}
|
|
|
|
addTo := func(slice []string, add ...string) []string {
|
|
slice = append(slice, add...)
|
|
sort.Strings(slice)
|
|
return slice
|
|
}
|
|
|
|
imgSrc := source.Description{
|
|
Metadata: source.ImageMetadata{},
|
|
}
|
|
|
|
dirSrc := source.Description{
|
|
Metadata: source.DirectoryMetadata{},
|
|
}
|
|
|
|
fileSrc := source.Description{
|
|
Metadata: source.FileMetadata{},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
src source.Description
|
|
cfg *CreateSBOMConfig
|
|
wantTaskNames [][]string
|
|
wantManifest *catalogerManifest
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "default catalogers for image source",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig(),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "default catalogers for directory source",
|
|
src: dirSrc,
|
|
cfg: DefaultCreateSBOMConfig(),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"directory"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "directory"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
// note, the file source acts like a directory scan
|
|
name: "default catalogers for file source",
|
|
src: fileSrc,
|
|
cfg: DefaultCreateSBOMConfig(),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"directory"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "directory"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "no file digest cataloger",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithHashers()),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "select no file catalogers",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithSelection(file.NoFilesSelection)),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
// note: there are no file catalogers in their own group
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "select all file catalogers",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithFilesConfig(filecataloging.DefaultConfig().WithSelection(file.AllFilesSelection)),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
// note: there is a single group of catalogers for pkgs and files
|
|
append(
|
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
fileCatalogerNames(true, true, true)...,
|
|
),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "user-provided persistent cataloger is always run (image)",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
|
pkgcataloging.NewAlwaysEnabledCatalogerReference(newDummyCataloger("persistent")),
|
|
),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "user-provided persistent cataloger is always run (directory)",
|
|
src: dirSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
|
pkgcataloging.NewAlwaysEnabledCatalogerReference(newDummyCataloger("persistent")),
|
|
),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"directory"},
|
|
},
|
|
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "user-provided persistent cataloger is always run (user selection does not affect this)",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
|
pkgcataloging.NewAlwaysEnabledCatalogerReference(newDummyCataloger("persistent")),
|
|
).WithCatalogerSelection(pkgcataloging.NewSelectionRequest().WithSubSelections("javascript")),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
SubSelectTags: []string{"javascript"},
|
|
},
|
|
Used: addTo(pkgIntersect("image", "javascript"), "persistent"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "user-provided cataloger runs when selected",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
|
pkgcataloging.NewCatalogerReference(newDummyCataloger("user-provided"), []string{"image"}),
|
|
),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
{
|
|
name: "user-provided cataloger NOT run when NOT selected",
|
|
src: imgSrc,
|
|
cfg: DefaultCreateSBOMConfig().WithCatalogers(
|
|
pkgcataloging.NewCatalogerReference(newDummyCataloger("user-provided"), []string{"bogus-selector-will-never-be-used"}),
|
|
),
|
|
wantTaskNames: [][]string{
|
|
environmentCatalogerNames(),
|
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
fileCatalogerNames(true, true, true),
|
|
relationshipCatalogerNames(),
|
|
},
|
|
wantManifest: &catalogerManifest{
|
|
Requested: pkgcataloging.SelectionRequest{
|
|
DefaultNamesOrTags: []string{"image"},
|
|
},
|
|
Used: pkgCatalogerNamesWithTagOrName(t, "image"),
|
|
},
|
|
wantErr: require.NoError,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.wantErr == nil {
|
|
tt.wantErr = require.NoError
|
|
}
|
|
|
|
// sanity check
|
|
require.NotEmpty(t, tt.wantTaskNames)
|
|
for _, group := range tt.wantTaskNames {
|
|
require.NotEmpty(t, group)
|
|
}
|
|
|
|
// test the subject
|
|
gotTasks, gotManifest, err := tt.cfg.makeTaskGroups(tt.src)
|
|
tt.wantErr(t, err)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
gotNames := taskGroupNames(gotTasks)
|
|
|
|
if d := cmp.Diff(
|
|
tt.wantTaskNames,
|
|
gotNames,
|
|
// order within a group does not matter
|
|
cmpopts.SortSlices(func(a, b string) bool {
|
|
return a < b
|
|
}),
|
|
); d != "" {
|
|
t.Errorf("mismatched task group names (-want +got):\n%s", d)
|
|
}
|
|
|
|
if d := cmp.Diff(tt.wantManifest, gotManifest); d != "" {
|
|
t.Errorf("mismatched cataloger manifest (-want +got):\n%s", d)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func pkgCatalogerNamesWithTagOrName(t *testing.T, token string) []string {
|
|
var names []string
|
|
cfg := task.DefaultCatalogingFactoryConfig()
|
|
for _, factory := range task.DefaultPackageTaskFactories() {
|
|
cat := factory(cfg)
|
|
|
|
name := cat.Name()
|
|
|
|
if selector, ok := cat.(task.Selector); ok {
|
|
if selector.HasAllSelectors(token) {
|
|
names = append(names, name)
|
|
continue
|
|
}
|
|
}
|
|
if name == token {
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
|
|
// these thresholds are arbitrary but should be large enough to catch any major changes
|
|
switch token {
|
|
case "image":
|
|
require.Greater(t, len(names), 18, "minimum cataloger sanity check failed token")
|
|
case "directory":
|
|
require.Greater(t, len(names), 25, "minimum cataloger sanity check failed token")
|
|
default:
|
|
require.Greater(t, len(names), 0, "minimum cataloger sanity check failed token")
|
|
}
|
|
|
|
sort.Strings(names)
|
|
return names
|
|
}
|
|
|
|
func fileCatalogerNames(digest, metadata, executable bool) []string {
|
|
var names []string
|
|
if digest {
|
|
names = append(names, "file-digest-cataloger")
|
|
}
|
|
if executable {
|
|
names = append(names, "file-executable-cataloger")
|
|
}
|
|
if metadata {
|
|
names = append(names, "file-metadata-cataloger")
|
|
}
|
|
return names
|
|
}
|
|
|
|
func relationshipCatalogerNames() []string {
|
|
return []string{"relationships-cataloger"}
|
|
}
|
|
|
|
func environmentCatalogerNames() []string {
|
|
return []string{"environment-cataloger"}
|
|
}
|
|
|
|
func taskGroupNames(groups [][]task.Task) [][]string {
|
|
var names [][]string
|
|
for _, group := range groups {
|
|
var groupNames []string
|
|
for _, tsk := range group {
|
|
groupNames = append(groupNames, tsk.Name())
|
|
}
|
|
names = append(names, groupNames)
|
|
}
|
|
return names
|
|
}
|
|
|
|
func Test_replaceDefaultTagReferences(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
lst []string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "no default tag",
|
|
lst: []string{"foo", "bar"},
|
|
want: []string{"foo", "bar"},
|
|
},
|
|
{
|
|
name: "replace default tag",
|
|
lst: []string{"foo", "default", "bar"},
|
|
want: []string{"foo", "replacement", "bar"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, replaceDefaultTagReferences("replacement", tt.lst))
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_findDefaultTag(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
src source.Description
|
|
want string
|
|
wantErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "image",
|
|
src: source.Description{
|
|
Metadata: source.ImageMetadata{},
|
|
},
|
|
want: pkgcataloging.ImageTag,
|
|
},
|
|
{
|
|
name: "directory",
|
|
src: source.Description{
|
|
Metadata: source.DirectoryMetadata{},
|
|
},
|
|
want: pkgcataloging.DirectoryTag,
|
|
},
|
|
{
|
|
name: "file",
|
|
src: source.Description{
|
|
Metadata: source.FileMetadata{},
|
|
},
|
|
want: pkgcataloging.DirectoryTag, // not a mistake...
|
|
},
|
|
{
|
|
name: "unknown",
|
|
src: source.Description{
|
|
Metadata: struct{}{},
|
|
},
|
|
wantErr: require.Error,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.wantErr == nil {
|
|
tt.wantErr = require.NoError
|
|
}
|
|
got, err := findDefaultTag(tt.src)
|
|
tt.wantErr(t, err)
|
|
if err != nil {
|
|
return
|
|
}
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateSBOMConfig_validate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg *CreateSBOMConfig
|
|
wantErr assert.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "incompatible ExcludeBinaryPackagesWithFileOwnershipOverlap selection",
|
|
cfg: DefaultCreateSBOMConfig().
|
|
WithRelationshipsConfig(
|
|
cataloging.DefaultRelationshipsConfig().
|
|
WithExcludeBinaryPackagesWithFileOwnershipOverlap(true).
|
|
WithPackageFileOwnershipOverlap(false),
|
|
),
|
|
wantErr: assert.Error,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.wantErr == nil {
|
|
tt.wantErr = assert.NoError
|
|
}
|
|
tt.wantErr(t, tt.cfg.validate())
|
|
})
|
|
}
|
|
}
|