diff --git a/cmd/syft/cli/options/catalog.go b/cmd/syft/cli/options/catalog.go index 559df10b71e..9ddd2c958e7 100644 --- a/cmd/syft/cli/options/catalog.go +++ b/cmd/syft/cli/options/catalog.go @@ -75,8 +75,11 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) syft.CreateSBOMConfig { WithDataGenerationConfig(cfg.ToDataGenerationConfig()). WithPackagesConfig(cfg.ToPackagesConfig()). WithFilesConfig(cfg.ToFilesConfig()). - WithDefaultCatalogers(cfg.DefaultCatalogers...). - WithCatalogerSelection(cfg.SelectCatalogers...) + WithCatalogerSelection( + pkgcataloging.NewSelectionRequest(). + WithDefaults(cfg.DefaultCatalogers...). + WithExpression(cfg.SelectCatalogers...), + ) } func (cfg Catalog) ToSearchConfig() cataloging.SearchConfig { diff --git a/internal/task/expression.go b/internal/task/expression.go index 80902584622..e99551c6f3c 100644 --- a/internal/task/expression.go +++ b/internal/task/expression.go @@ -4,10 +4,11 @@ import ( "fmt" "regexp" "sort" - "strings" "github.com/hashicorp/go-multierror" "github.com/scylladb/go-set/strset" + + "github.com/anchore/syft/syft/cataloging/pkgcataloging" ) var expressionNodePattern = regexp.MustCompile(`^([a-zA-Z0-9][a-zA-Z0-9-+]*)+$`) @@ -70,12 +71,6 @@ type expressionContext struct { Tags *strset.Set } -func badExpression(exp string, op Operation, err error) Expression { - return Expression{ - Errors: []error{newErrInvalidExpression(exp, op, err)}, - } -} - func newExpressionContext(ts []Task) *expressionContext { ec := &expressionContext{ Names: strset.New(tasks(ts).Names()...), @@ -89,6 +84,22 @@ func newExpressionContext(ts []Task) *expressionContext { // newExpression creates a new validated Expression object relative to the task names and tags. func (ec expressionContext) newExpression(exp string, operation Operation, token string) Expression { + if token == "" { + return Expression{ + Operation: operation, + Operand: token, + Errors: []error{newErrInvalidExpression(exp, operation, ErrEmptyToken)}, + } + } + + if !isValidNode(token) { + return Expression{ + Operation: operation, + Operand: token, + Errors: []error{newErrInvalidExpression(exp, operation, ErrInvalidToken)}, + } + } + var err error switch operation { case SetOperation, RemoveOperation: @@ -131,63 +142,27 @@ func (ec expressionContext) newExpression(exp string, operation Operation, token } } -// parseExpressions parses a list of string expressions, provided as two distinct sets (set operations and all other operations), -// and returns a list of sorted and validated Expression objects. -func parseExpressions(nc *expressionContext, basis, expressions []string) Expressions { +func newExpressionsFromSelectionRequest(nc *expressionContext, selectionRequest pkgcataloging.SelectionRequest) Expressions { var all Expressions - all = append(all, processOperatorSet(nc, basis, SetOperation)...) - all = append(all, processOperatorSet(nc, expressions, SubSelectOperation)...) - - sort.Sort(all) - - return all -} - -func processOperatorSet(nc *expressionContext, expressions []string, defaultOperator Operation) []Expression { - var all []Expression - for _, exp := range expressions { - exp = strings.TrimSpace(exp) - - operator, value := splitOnOperator(exp) - - if defaultOperator == SetOperation && operator != "" { - all = append(all, badExpression(exp, operator, ErrInvalidOperator)) - continue - } - - if value == "" { - all = append(all, badExpression(exp, operator, ErrEmptyToken)) - continue - } - - if operator == "" { - operator = defaultOperator - } - - if !isValidNode(value) { - all = append(all, badExpression(exp, operator, ErrInvalidToken)) - continue - } - - all = append(all, nc.newExpression(exp, operator, value)) + for _, exp := range selectionRequest.DefaultNamesOrTags { + all = append(all, nc.newExpression(exp, SetOperation, exp)) } - return all -} + for _, exp := range selectionRequest.SubSelectTags { + all = append(all, nc.newExpression(exp, SubSelectOperation, exp)) + } -func splitOnOperator(s string) (Operation, string) { - if len(s) == 0 { - return "", "" + for _, exp := range selectionRequest.AddNames { + all = append(all, nc.newExpression(exp, AddOperation, exp)) } - switch s[0] { - case '+': - return AddOperation, s[1:] - case '-': - return RemoveOperation, s[1:] + for _, exp := range selectionRequest.RemoveNamesOrTags { + all = append(all, nc.newExpression(exp, RemoveOperation, exp)) } - return "", s + + sort.Sort(all) + return all } func isValidNode(s string) bool { diff --git a/internal/task/expression_test.go b/internal/task/expression_test.go index 7b69fae670f..e1a97ab2213 100644 --- a/internal/task/expression_test.go +++ b/internal/task/expression_test.go @@ -6,9 +6,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/cataloging/pkgcataloging" ) -func TestParseExpressions(t *testing.T) { +func Test_newExpressionsFromSelectionRequest(t *testing.T) { ts := []Task{ dummyTask("1", "t1"), dummyTask("2", "t2"), @@ -89,14 +91,14 @@ func TestParseExpressions(t *testing.T) { basis: []string{"+1"}, expressions: []string{}, expected: nil, - expectedErrors: []error{ErrInvalidOperator}, + expectedErrors: []error{ErrInvalidToken}, }, { name: "use - operator in basis", basis: []string{"-1"}, expressions: []string{}, expected: nil, - expectedErrors: []error{ErrInvalidOperator}, + expectedErrors: []error{ErrInvalidToken}, }, { name: "invalid name", @@ -133,7 +135,9 @@ func TestParseExpressions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := parseExpressions(nc, tt.basis, tt.expressions) + req := pkgcataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...) + + result := newExpressionsFromSelectionRequest(nc, req) if tt.expectedErrors != nil { errs := result.Errors() require.Len(t, errs, len(tt.expectedErrors)) diff --git a/internal/task/package_task_factory.go b/internal/task/package_task_factory.go index f911f8c5146..6cbd4feaea7 100644 --- a/internal/task/package_task_factory.go +++ b/internal/task/package_task_factory.go @@ -150,7 +150,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string return nil } - tags = append(tags, PackageTag) + tags = append(tags, pkgcataloging.PackageTag) return NewTask(c.Name(), fn, tags...) } diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index f0ceaec264e..424098ee547 100644 --- a/internal/task/package_tasks.go +++ b/internal/task/package_tasks.go @@ -1,6 +1,7 @@ package task import ( + "github.com/anchore/syft/syft/cataloging/pkgcataloging" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/alpine" "github.com/anchore/syft/syft/pkg/cataloger/arch" @@ -32,84 +33,84 @@ import ( func DefaultPackageTaskFactories() PackageTaskFactories { return []packageTaskFactory{ // OS package installed catalogers /////////////////////////////////////////////////////////////////////////// - newSimplePackageTaskFactory(arch.NewDBCataloger, DirectoryTag, InstalledTag, ImageTag, OSTag, "linux", "alpm", "archlinux"), - newSimplePackageTaskFactory(alpine.NewDBCataloger, DirectoryTag, InstalledTag, ImageTag, OSTag, "linux", "apk", "alpine"), - newSimplePackageTaskFactory(debian.NewDBCataloger, DirectoryTag, InstalledTag, ImageTag, OSTag, "linux", "dpkg", "debian"), - newSimplePackageTaskFactory(gentoo.NewPortageCataloger, DirectoryTag, InstalledTag, ImageTag, OSTag, "linux", "portage", "gentoo"), - newSimplePackageTaskFactory(redhat.NewDBCataloger, DirectoryTag, InstalledTag, ImageTag, OSTag, "linux", "rpm", "redhat"), + newSimplePackageTaskFactory(arch.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "alpm", "archlinux"), + newSimplePackageTaskFactory(alpine.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "apk", "alpine"), + newSimplePackageTaskFactory(debian.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "dpkg", "debian"), + newSimplePackageTaskFactory(gentoo.NewPortageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "portage", "gentoo"), + newSimplePackageTaskFactory(redhat.NewDBCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.OSTag, "linux", "rpm", "redhat"), // OS package declared catalogers /////////////////////////////////////////////////////////////////////////// - newSimplePackageTaskFactory(redhat.NewArchiveCataloger, DeclaredTag, DirectoryTag, OSTag, "linux", "rpm", "redhat"), + newSimplePackageTaskFactory(redhat.NewArchiveCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.OSTag, "linux", "rpm", "redhat"), // language-specific package installed catalogers /////////////////////////////////////////////////////////////////////////// - newSimplePackageTaskFactory(cpp.NewConanInfoCataloger, InstalledTag, ImageTag, LanguageTag, "cpp", "conan"), - newSimplePackageTaskFactory(javascript.NewPackageCataloger, InstalledTag, ImageTag, LanguageTag, "javascript", "node"), - newSimplePackageTaskFactory(php.NewComposerInstalledCataloger, InstalledTag, ImageTag, LanguageTag, "php", "composer"), - newSimplePackageTaskFactory(r.NewPackageCataloger, InstalledTag, ImageTag, LanguageTag, "r"), - newSimplePackageTaskFactory(ruby.NewInstalledGemSpecCataloger, InstalledTag, ImageTag, LanguageTag, "ruby", "gem", "gemspec"), - newSimplePackageTaskFactory(rust.NewAuditBinaryCataloger, InstalledTag, ImageTag, LanguageTag, "rust", "binary"), + newSimplePackageTaskFactory(cpp.NewConanInfoCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "cpp", "conan"), + newSimplePackageTaskFactory(javascript.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "javascript", "node"), + newSimplePackageTaskFactory(php.NewComposerInstalledCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "php", "composer"), + newSimplePackageTaskFactory(r.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "r"), + newSimplePackageTaskFactory(ruby.NewInstalledGemSpecCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "ruby", "gem", "gemspec"), + newSimplePackageTaskFactory(rust.NewAuditBinaryCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "rust", "binary"), // language-specific package declared catalogers /////////////////////////////////////////////////////////////////////////// - newSimplePackageTaskFactory(cpp.NewConanCataloger, DeclaredTag, DirectoryTag, LanguageTag, "cpp", "conan"), - newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "dart"), - newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, DeclaredTag, DirectoryTag, LanguageTag, "dotnet", "c#"), - newSimplePackageTaskFactory(elixir.NewMixLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "elixir"), - newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "erlang"), - newSimplePackageTaskFactory(haskell.NewHackageCataloger, DeclaredTag, DirectoryTag, LanguageTag, "haskell", "hackage", "cabal"), + newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"), + newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"), + newSimplePackageTaskFactory(dotnet.NewDotnetDepsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dotnet", "c#"), + newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"), + newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"), + newSimplePackageTaskFactory(haskell.NewHackageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "haskell", "hackage", "cabal"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return golang.NewGoModuleFileCataloger(cfg.PackagesConfig.Golang) }, - DeclaredTag, DirectoryTag, LanguageTag, "go", "golang", "gomod", + pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "go", "golang", "gomod", ), - newSimplePackageTaskFactory(java.NewGradleLockfileCataloger, DeclaredTag, DirectoryTag, LanguageTag, "java", "gradle"), + newSimplePackageTaskFactory(java.NewGradleLockfileCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "java", "gradle"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return java.NewPomCataloger(cfg.PackagesConfig.JavaArchive) }, - DeclaredTag, DirectoryTag, LanguageTag, "java", "maven", + pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "java", "maven", ), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return javascript.NewLockCataloger(cfg.PackagesConfig.Javascript) }, - DeclaredTag, DirectoryTag, LanguageTag, "javascript", "node", "npm", + pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "javascript", "node", "npm", ), - newSimplePackageTaskFactory(php.NewComposerLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "php", "composer"), + newSimplePackageTaskFactory(php.NewComposerLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "php", "composer"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return python.NewPackageCataloger(cfg.PackagesConfig.Python) }, - DeclaredTag, DirectoryTag, LanguageTag, "python", + pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "python", ), - newSimplePackageTaskFactory(ruby.NewGemFileLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "ruby", "gem"), - newSimplePackageTaskFactory(ruby.NewGemSpecCataloger, DeclaredTag, DirectoryTag, LanguageTag, "ruby", "gem", "gemspec"), - newSimplePackageTaskFactory(rust.NewCargoLockCataloger, DeclaredTag, DirectoryTag, LanguageTag, "rust", "cargo"), - newSimplePackageTaskFactory(swift.NewCocoapodsCataloger, DeclaredTag, DirectoryTag, LanguageTag, "swift", "cocoapods"), - newSimplePackageTaskFactory(swift.NewSwiftPackageManagerCataloger, DeclaredTag, DirectoryTag, LanguageTag, "swift", "spm"), + newSimplePackageTaskFactory(ruby.NewGemFileLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "ruby", "gem"), + newSimplePackageTaskFactory(ruby.NewGemSpecCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "ruby", "gem", "gemspec"), + newSimplePackageTaskFactory(rust.NewCargoLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "rust", "cargo"), + newSimplePackageTaskFactory(swift.NewCocoapodsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "cocoapods"), + newSimplePackageTaskFactory(swift.NewSwiftPackageManagerCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "spm"), // language-specific package for both image and directory scans (but not necessarily declared) //////////////////////////////////////// - newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, DirectoryTag, InstalledTag, ImageTag, LanguageTag, "dotnet", "c#", "binary"), - newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, DirectoryTag, InstalledTag, ImageTag, LanguageTag, "python"), + newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"), + newSimplePackageTaskFactory(python.NewInstalledPackageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "python"), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return golang.NewGoModuleBinaryCataloger(cfg.PackagesConfig.Golang) }, - DirectoryTag, InstalledTag, ImageTag, LanguageTag, "go", "golang", "gomod", "binary", + pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "go", "golang", "gomod", "binary", ), newPackageTaskFactory( func(cfg CatalogingFactoryConfig) pkg.Cataloger { return java.NewArchiveCataloger(cfg.PackagesConfig.JavaArchive) }, - DirectoryTag, InstalledTag, ImageTag, LanguageTag, "java", "maven", + pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "java", "maven", ), - newSimplePackageTaskFactory(java.NewNativeImageCataloger, DirectoryTag, InstalledTag, ImageTag, LanguageTag, "java"), - newSimplePackageTaskFactory(nix.NewStoreCataloger, DirectoryTag, InstalledTag, ImageTag, LanguageTag, "nix"), + newSimplePackageTaskFactory(java.NewNativeImageCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "java"), + newSimplePackageTaskFactory(nix.NewStoreCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "nix"), // other package catalogers /////////////////////////////////////////////////////////////////////////// - newSimplePackageTaskFactory(binary.NewCataloger, DeclaredTag, DirectoryTag, InstalledTag, ImageTag, "binary"), - newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, DeclaredTag, DirectoryTag, "github", "github-actions"), - newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, DeclaredTag, DirectoryTag, "github", "github-actions"), - newSimplePackageTaskFactory(sbomCataloger.NewCataloger, ImageTag, DeclaredTag, DirectoryTag, ImageTag, "sbom"), // note: not evidence of installed packages + newSimplePackageTaskFactory(binary.NewCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "binary"), + newSimplePackageTaskFactory(githubactions.NewActionUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"), + newSimplePackageTaskFactory(githubactions.NewWorkflowUsageCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "github", "github-actions"), + newSimplePackageTaskFactory(sbomCataloger.NewCataloger, pkgcataloging.ImageTag, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "sbom"), // note: not evidence of installed packages } } diff --git a/internal/task/selection.go b/internal/task/selection.go index 994db047fd2..bd0cacdfbeb 100644 --- a/internal/task/selection.go +++ b/internal/task/selection.go @@ -7,25 +7,17 @@ import ( "github.com/scylladb/go-set/strset" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/cataloging/pkgcataloging" ) // Selection represents the users request for a subset of tasks to run and the resulting set of task names that were // selected. Additionally, all tokens that were matched on to reach the returned conclusion are also provided. type Selection struct { - Request SelectionRequest + Request pkgcataloging.SelectionRequest Result *strset.Set TokensByTask map[string]TokenSelection } -// SelectionRequest contains the original user request for a subset of tasks to run as two distinct sets. This also -// contains the parsed expressions relative to a given set of tasks to help understand how it was interpreted -// and if ultimately the user request was valid. -type SelectionRequest struct { - Default []string `json:"default"` - Selection []string `json:"selection"` - Expressions Expressions `json:"-"` -} - // TokenSelection represents the tokens that were matched on to either include or exclude a given task (based on expression evaluation). type TokenSelection struct { SelectedOn *strset.Set @@ -50,53 +42,30 @@ func (ts *TokenSelection) merge(other ...TokenSelection) { } } -func newEmptySelection(nodes Expressions) Selection { +func newSelection() Selection { return Selection{ Result: strset.New(), TokensByTask: make(map[string]TokenSelection), - Request: newSelectionRequest(nodes), - } -} - -func newSelectionRequest(e Expressions) SelectionRequest { - var ( - // this might be used in JSON output, so collections must be allocated - basis = make([]string, 0) - selections = make([]string, 0) - ) - for _, n := range e { - if len(n.Errors) > 0 { - continue - } - switch n.Operation { - case SetOperation: - basis = append(basis, n.Operand) - case SubSelectOperation, AddOperation, RemoveOperation: - selections = append(selections, n.String()) - } - } - return SelectionRequest{ - Expressions: e, - Default: basis, - Selection: selections, } } // Select parses the given expressions as two sets: expressions that represent a "set" operation, and expressions that // represent all other operations. The parsed expressions are then evaluated against the given tasks to return // a subset (or the same) set of tasks. -func Select(allTasks []Task, basis, expressions []string) ([]Task, Selection, error) { - nodes := parseExpressions(newExpressionContext(allTasks), basis, expressions) +func Select(allTasks []Task, selectionRequest pkgcataloging.SelectionRequest) ([]Task, Selection, error) { + nodes := newExpressionsFromSelectionRequest(newExpressionContext(allTasks), selectionRequest) finalTasks, selection := selectByExpressions(allTasks, nodes) + selection.Request = selectionRequest + return finalTasks, selection, nodes.Validate() } // selectByExpressions the set of tasks to run based on the given expression(s). func selectByExpressions(ts tasks, nodes Expressions) (tasks, Selection) { if len(nodes) == 0 { - return ts, newEmptySelection(nodes) + return ts, newSelection() } finalSet := newSet() @@ -151,7 +120,6 @@ func selectByExpressions(ts tasks, nodes Expressions) (tasks, Selection) { return finalTasks, Selection{ Result: strset.New(finalTasks.Names()...), TokensByTask: allSelections, - Request: newSelectionRequest(nodes), } } diff --git a/internal/task/selection_test.go b/internal/task/selection_test.go index 8e6f711d6c7..c91b77bc4c6 100644 --- a/internal/task/selection_test.go +++ b/internal/task/selection_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/internal/sbomsync" + "github.com/anchore/syft/syft/cataloging/pkgcataloging" "github.com/anchore/syft/syft/file" ) @@ -70,7 +71,7 @@ func TestSelect(t *testing.T) { expressions []string wantNames []string wantTokens map[string]TokenSelection - wantRequest SelectionRequest + wantRequest pkgcataloging.SelectionRequest wantErr assert.ErrorAssertionFunc }{ { @@ -80,10 +81,7 @@ func TestSelect(t *testing.T) { expressions: []string{}, wantNames: []string{}, wantTokens: map[string]TokenSelection{}, - wantRequest: SelectionRequest{ - Default: []string{}, - Selection: []string{}, - }, + wantRequest: pkgcataloging.SelectionRequest{}, }, { name: "use default tasks", @@ -130,16 +128,8 @@ func TestSelect(t *testing.T) { "binary-cataloger": newTokenSelection([]string{"image"}, nil), "sbom-cataloger": newTokenSelection([]string{"image"}, nil), }, - wantRequest: SelectionRequest{ - Expressions: []Expression{ - { - Operation: SetOperation, - Operand: "image", - Errors: nil, - }, - }, - Default: []string{"image"}, - Selection: []string{}, + wantRequest: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, }, { @@ -184,35 +174,11 @@ func TestSelect(t *testing.T) { "binary-cataloger": newTokenSelection([]string{"image"}, nil), "sbom-cataloger": newTokenSelection([]string{"image"}, nil), }, - wantRequest: SelectionRequest{ - Expressions: []Expression{ - { - Operation: SetOperation, - Operand: "image", - Errors: nil, - }, - { - Operation: SubSelectOperation, - Operand: "os", - Errors: nil, - }, - { - Operation: RemoveOperation, - Operand: "dpkg", - Errors: nil, - }, - { - Operation: AddOperation, - Operand: "github-actions-usage-cataloger", - Errors: nil, - }, - }, - Default: []string{"image"}, - Selection: []string{ - "os", - "-dpkg", - "+github-actions-usage-cataloger", - }, + wantRequest: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, + SubSelectTags: []string{"os"}, + RemoveNamesOrTags: []string{"dpkg"}, + AddNames: []string{"github-actions-usage-cataloger"}, }, }, { @@ -261,47 +227,11 @@ func TestSelect(t *testing.T) { "binary-cataloger": newTokenSelection([]string{"image"}, nil), "sbom-cataloger": newTokenSelection([]string{"image"}, nil), }, - wantRequest: SelectionRequest{ - // note: this is an ordered expression (based on operation primarily) - Expressions: []Expression{ - { - Operation: SetOperation, - Operand: "image", - Errors: nil, - }, - { - Operation: SubSelectOperation, - Operand: "os", - Errors: nil, - }, - { - Operation: SubSelectOperation, - Operand: "rust-cargo-lock-cataloger", - Errors: []error{newErrInvalidExpression("rust-cargo-lock-cataloger", SubSelectOperation, ErrNamesNotAllowed)}, - }, - { - Operation: RemoveOperation, - Operand: "dpkg", - Errors: nil, - }, - { - Operation: AddOperation, - Operand: "github-actions-usage-cataloger", - Errors: nil, - }, - { - Operation: AddOperation, - Operand: "python", - Errors: []error{newErrInvalidExpression("+python", AddOperation, ErrTagsNotAllowed)}, - }, - }, - Default: []string{"image"}, - Selection: []string{ - // note: nodes that have errors are not included in the selection - "os", - "-dpkg", - "+github-actions-usage-cataloger", - }, + wantRequest: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, + SubSelectTags: []string{"os", "rust-cargo-lock-cataloger"}, + RemoveNamesOrTags: []string{"dpkg"}, + AddNames: []string{"github-actions-usage-cataloger", "python"}, }, wantErr: assert.Error, // !important! }, @@ -368,16 +298,8 @@ func TestSelect(t *testing.T) { "github-action-workflow-usage-cataloger": newTokenSelection([]string{"all"}, nil), "sbom-cataloger": newTokenSelection([]string{"all"}, nil), }, - wantRequest: SelectionRequest{ - Expressions: []Expression{ - { - Operation: SetOperation, - Operand: "all", - Errors: nil, - }, - }, - Default: []string{"all"}, - Selection: []string{}, + wantRequest: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"all"}, }, }, { @@ -396,21 +318,8 @@ func TestSelect(t *testing.T) { "ruby-installed-gemspec-cataloger": newTokenSelection([]string{"gemspec"}, nil), "python-installed-package-cataloger": newTokenSelection([]string{"python"}, nil), }, - wantRequest: SelectionRequest{ - Expressions: []Expression{ - { - Operation: SetOperation, - Operand: "gemspec", - Errors: nil, - }, - { - Operation: SetOperation, - Operand: "python", - Errors: nil, - }, - }, - Default: []string{"gemspec", "python"}, - Selection: []string{}, + wantRequest: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"gemspec", "python"}, }, }, } @@ -419,7 +328,10 @@ func TestSelect(t *testing.T) { if tt.wantErr == nil { tt.wantErr = assert.NoError } - got, gotEvidence, err := Select(tt.allTasks, tt.basis, tt.expressions) + + req := pkgcataloging.NewSelectionRequest().WithDefaults(tt.basis...).WithExpression(tt.expressions...) + + got, gotEvidence, err := Select(tt.allTasks, req) tt.wantErr(t, err) if err != nil { // dev note: this is useful for debugging when needed... diff --git a/syft/cataloging/pkgcataloging/selection_request.go b/syft/cataloging/pkgcataloging/selection_request.go new file mode 100644 index 00000000000..7dc711e3ae7 --- /dev/null +++ b/syft/cataloging/pkgcataloging/selection_request.go @@ -0,0 +1,65 @@ +package pkgcataloging + +import ( + "strings" +) + +type SelectionRequest struct { + DefaultNamesOrTags []string `json:"default,omitempty"` + SubSelectTags []string `json:"selection,omitempty"` + AddNames []string `json:"addition,omitempty"` + RemoveNamesOrTags []string `json:"removal,omitempty"` +} + +func NewSelectionRequest() SelectionRequest { + return SelectionRequest{} +} + +func (s SelectionRequest) WithExpression(expressions ...string) SelectionRequest { + expressions = cleanSelection(expressions) + for _, expr := range expressions { + switch { + case strings.HasPrefix(expr, "+"): + s = s.WithAdditions(strings.TrimPrefix(expr, "+")) + case strings.HasPrefix(expr, "-"): + s = s.WithRemovals(strings.TrimPrefix(expr, "-")) + default: + s = s.WithSubSelections(expr) + } + } + return s +} + +func (s SelectionRequest) WithDefaults(nameOrTags ...string) SelectionRequest { + s.DefaultNamesOrTags = append(s.DefaultNamesOrTags, nameOrTags...) + return s +} + +func (s SelectionRequest) WithSubSelections(tags ...string) SelectionRequest { + s.SubSelectTags = append(s.SubSelectTags, tags...) + return s +} + +func (s SelectionRequest) WithAdditions(names ...string) SelectionRequest { + s.AddNames = append(s.AddNames, names...) + return s +} + +func (s SelectionRequest) WithRemovals(nameOrTags ...string) SelectionRequest { + s.RemoveNamesOrTags = append(s.RemoveNamesOrTags, nameOrTags...) + return s +} + +func cleanSelection(tags []string) []string { + var cleaned []string + for _, tag := range tags { + for _, t := range strings.Split(tag, ",") { + t = strings.TrimSpace(t) + if t == "" { + continue + } + cleaned = append(cleaned, t) + } + } + return cleaned +} diff --git a/internal/task/tags.go b/syft/cataloging/pkgcataloging/tags.go similarity index 97% rename from internal/task/tags.go rename to syft/cataloging/pkgcataloging/tags.go index c579df2e9ce..5c7b46bb077 100644 --- a/internal/task/tags.go +++ b/syft/cataloging/pkgcataloging/tags.go @@ -1,4 +1,4 @@ -package task +package pkgcataloging const ( // InstalledTag is to identify packages found to be positively installed. diff --git a/syft/configuration_audit_trail.go b/syft/configuration_audit_trail.go index 6b993025c7b..78f43fece61 100644 --- a/syft/configuration_audit_trail.go +++ b/syft/configuration_audit_trail.go @@ -4,7 +4,6 @@ import ( "encoding/json" "reflect" - "github.com/anchore/syft/internal/task" "github.com/anchore/syft/syft/cataloging" "github.com/anchore/syft/syft/cataloging/filecataloging" "github.com/anchore/syft/syft/cataloging/pkgcataloging" @@ -22,8 +21,8 @@ type configurationAuditTrail struct { } type catalogerManifest struct { - Requested task.SelectionRequest `json:"requested" yaml:"requested" mapstructure:"requested"` - Used []string `json:"used" yaml:"used" mapstructure:"used"` + Requested pkgcataloging.SelectionRequest `json:"requested" yaml:"requested" mapstructure:"requested"` + Used []string `json:"used" yaml:"used" mapstructure:"used"` } type marshalAPIConfiguration configurationAuditTrail @@ -46,6 +45,11 @@ func (cfg configurationAuditTrail) MarshalJSON() ([]byte, error) { return nil, err } + if v, exists := dataMap["extra"]; exists && v == nil { + // remove the extra key if it renders as nil + delete(dataMap, "extra") + } + return marshalSorted(dataMap) } diff --git a/syft/create_sbom_config.go b/syft/create_sbom_config.go index b55e1e2641c..8459f7a3a1d 100644 --- a/syft/create_sbom_config.go +++ b/syft/create_sbom_config.go @@ -17,22 +17,21 @@ import ( // CreateSBOMConfig specifies all parameters needed for creating an SBOM. type CreateSBOMConfig struct { // required configuration input to specify how cataloging should be performed - Search cataloging.SearchConfig - Relationships cataloging.RelationshipsConfig - DataGeneration cataloging.DataGenerationConfig - Packages pkgcataloging.Config - Files filecataloging.Config - Parallelism int - DefaultCatalogersSelection []string - CatalogersSelection []string + Search cataloging.SearchConfig + Relationships cataloging.RelationshipsConfig + DataGeneration cataloging.DataGenerationConfig + Packages pkgcataloging.Config + Files filecataloging.Config + Parallelism int + CatalogerSelection pkgcataloging.SelectionRequest // audit what tool is being used to generate the SBOM ToolName string ToolVersion string ToolConfiguration interface{} - packageTaskFactories task.PackageTaskFactories // syft default + user-provided (WithPersistentCatalogers() - persistentPackageTaskFactories task.PackageTaskFactories // user-provided (WithCataloger()) + packageTaskFactories task.PackageTaskFactories // syft default + user-provided catalogers with tags to select with + persistentPackageTaskFactories task.PackageTaskFactories // user-provided catalogers without tags to select with } func DefaultCreateSBOMConfig() CreateSBOMConfig { @@ -98,8 +97,8 @@ func (c CreateSBOMConfig) WithFilesConfig(cfg filecataloging.Config) CreateSBOMC return c } -// WithNoFiles allows for disabling file cataloging altogether. -func (c CreateSBOMConfig) WithNoFiles() CreateSBOMConfig { +// WithoutFiles allows for disabling file cataloging altogether. +func (c CreateSBOMConfig) WithoutFiles() CreateSBOMConfig { c.Files = filecataloging.Config{ Selection: file.NoFilesSelection, Hashers: nil, @@ -107,32 +106,23 @@ func (c CreateSBOMConfig) WithNoFiles() CreateSBOMConfig { return c } -// WithDefaultCatalogers allows for setting the default catalogers that should be used for cataloging. These catalogers -// can be selected by cataloger name or tag. If no default catalogers are specified, the catalogers will be selected -// based on the source type (e.g. image, directory, etc.). -func (c CreateSBOMConfig) WithDefaultCatalogers(set ...string) CreateSBOMConfig { - c.DefaultCatalogersSelection = cleanSelection(set) - return c -} - // WithCatalogerSelection allows for adding to, removing from, or sub-selecting the final set of catalogers by name or tag. -// - To sub-select catalogers (by tag) by providing strings that match task tags. (specifying cataloger names is not allowed) -// - To add a cataloger (by name) from the universal set of catalogers into the final set, prefix the cataloger name with "+". (specifying tags is not allowed) -// - To remove a cataloger (by name or tag) from the final set, prefix the cataloger name or tag with "-". -func (c CreateSBOMConfig) WithCatalogerSelection(selections ...string) CreateSBOMConfig { - c.CatalogersSelection = cleanSelection(selections) +func (c CreateSBOMConfig) WithCatalogerSelection(selection pkgcataloging.SelectionRequest) CreateSBOMConfig { + c.CatalogerSelection = selection return c } -// WithNoCatalogers removes all syft-implemented catalogers from the final set of catalogers. -func (c CreateSBOMConfig) WithNoCatalogers() CreateSBOMConfig { +// WithoutCatalogers removes all catalogers from the final set of catalogers. This is useful if you want to only use +// user-provided catalogers (without the default syft-provided catalogers). +func (c CreateSBOMConfig) WithoutCatalogers() CreateSBOMConfig { c.packageTaskFactories = nil + c.persistentPackageTaskFactories = nil return c } -// WithPersistentCatalogers allows for adding user-provided catalogers to the final set of catalogers that will always be run +// WithCatalogers allows for adding user-provided catalogers to the final set of catalogers that will always be run // regardless of the source type or any cataloger selections provided. -func (c CreateSBOMConfig) WithPersistentCatalogers(catalogers ...pkg.Cataloger) CreateSBOMConfig { +func (c CreateSBOMConfig) WithCatalogers(catalogers ...pkg.Cataloger) CreateSBOMConfig { for _, cat := range catalogers { c.persistentPackageTaskFactories = append(c.persistentPackageTaskFactories, func(cfg task.CatalogingFactoryConfig) task.Task { @@ -145,10 +135,15 @@ func (c CreateSBOMConfig) WithPersistentCatalogers(catalogers ...pkg.Cataloger) } // WithCataloger allows for adding a user-provided cataloger to the final set of catalogers that will conditionally -// be run based on the tags provided, the source type, and the user-provided cataloger selections. If you would like -// the given cataloger to be run with images, minimally provide the "image" tag. If you would like the given cataloger -// to be run with directories, minimally provide the "directory" tag. +// be run based on the tags provided, the source type, and the user-provided cataloger selections. For example, if you +// would like the given cataloger to be run against container images, minimally provide the "image" tag. If you would +// like the given cataloger to be run against file systems, minimally provide the "directory" tag. Providing no tags +// means that the cataloger will always be included in the final cataloger selection. func (c CreateSBOMConfig) WithCataloger(cat pkg.Cataloger, tags ...string) CreateSBOMConfig { + if len(tags) == 0 { + return c.WithCatalogers(cat) + } + c.packageTaskFactories = append(c.packageTaskFactories, func(cfg task.CatalogingFactoryConfig) task.Task { return task.NewPackageTask(cfg, cat, tags...) @@ -227,23 +222,21 @@ func (c CreateSBOMConfig) packageTasks(src source.Description) ([]task.Task, *ta return nil, nil, fmt.Errorf("unable to create package cataloger tasks: %w", err) } - defaultTag, err := findDefaultTag(src) - if err != nil { - return nil, nil, err - } + if len(c.CatalogerSelection.DefaultNamesOrTags) == 0 { + defaultTag, err := findDefaultTag(src) + if err != nil { + return nil, nil, fmt.Errorf("unable to determine default cataloger tag: %w", err) + } - basis := c.DefaultCatalogersSelection - if len(basis) == 0 { - // set the entire default cataloger set based on the source - basis = []string{defaultTag} - } else { - // replace "default" with a specific tag based on the source - basis = replaceDefaultTagReferences(defaultTag, basis) - } + if defaultTag != "" { + c.CatalogerSelection.DefaultNamesOrTags = append(c.CatalogerSelection.DefaultNamesOrTags, defaultTag) + } - catalogerSelection := replaceDefaultTagReferences(defaultTag, c.CatalogersSelection) + c.CatalogerSelection.RemoveNamesOrTags = replaceDefaultTagReferences(defaultTag, c.CatalogerSelection.RemoveNamesOrTags) + c.CatalogerSelection.SubSelectTags = replaceDefaultTagReferences(defaultTag, c.CatalogerSelection.SubSelectTags) + } - finalTasks, selection, err := task.Select(tsks, basis, catalogerSelection) + finalTasks, selection, err := task.Select(tsks, c.CatalogerSelection) if err != nil { return nil, nil, err } @@ -284,35 +277,6 @@ func (c CreateSBOMConfig) environmentTasks() []task.Task { return tsks } -func findDefaultTag(src source.Description) (string, error) { - switch m := src.Metadata.(type) { - case source.StereoscopeImageSourceMetadata: - return task.ImageTag, nil - case source.FileSourceMetadata, source.DirectorySourceMetadata: - return task.DirectoryTag, nil - default: - return "", fmt.Errorf("unable to determine cataloger defaults for source: %T", m) - } -} - -func replaceDefaultTagReferences(defaultTag string, lst []string) []string { - for i, tag := range lst { - if strings.TrimLeft(strings.ToLower(tag), "-+") != "default" { - continue - } - - switch { - case strings.HasPrefix(tag, "+"): - lst[i] = "+" + defaultTag - case strings.HasPrefix(tag, "-"): - lst[i] = "-" + defaultTag - default: - lst[i] = defaultTag - } - } - return lst -} - func (c CreateSBOMConfig) validate() error { if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap { if !c.Relationships.FileOwnershipOverlap { @@ -327,16 +291,22 @@ func (c CreateSBOMConfig) Create(src source.Source) (*sbom.SBOM, error) { return CreateSBOM(src, c) } -func cleanSelection(tags []string) []string { - var cleaned []string - for _, tag := range tags { - for _, t := range strings.Split(tag, ",") { - t = strings.TrimSpace(t) - if t == "" { - continue - } - cleaned = append(cleaned, t) +func findDefaultTag(src source.Description) (string, error) { + switch m := src.Metadata.(type) { + case source.StereoscopeImageSourceMetadata: + return pkgcataloging.ImageTag, nil + case source.FileSourceMetadata, source.DirectorySourceMetadata: + return pkgcataloging.DirectoryTag, nil + default: + return "", fmt.Errorf("unable to determine default cataloger tag for source type=%T", m) + } +} + +func replaceDefaultTagReferences(defaultTag string, lst []string) []string { + for i, tag := range lst { + if strings.ToLower(tag) == "default" { + lst[i] = defaultTag } } - return cleaned + return lst } diff --git a/syft/create_sbom_config_test.go b/syft/create_sbom_config_test.go index d6a5a4eeba8..ebc67ee302b 100644 --- a/syft/create_sbom_config_test.go +++ b/syft/create_sbom_config_test.go @@ -14,6 +14,7 @@ import ( "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" @@ -90,9 +91,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "image"), }, @@ -109,9 +109,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"directory"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"directory"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "directory"), }, @@ -129,9 +128,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"directory"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"directory"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "directory"), }, @@ -148,9 +146,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "image"), }, @@ -167,9 +164,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "image"), }, @@ -189,9 +185,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "image"), }, @@ -200,7 +195,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { { name: "user-provided persistent cataloger is always run (image)", src: imgSrc, - cfg: DefaultCreateSBOMConfig().WithPersistentCatalogers(newDummyCataloger("persistent")), + cfg: DefaultCreateSBOMConfig().WithCatalogers(newDummyCataloger("persistent")), wantTaskNames: [][]string{ environmentCatalogerNames(), addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"), @@ -208,9 +203,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"), }, @@ -219,7 +213,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { { name: "user-provided persistent cataloger is always run (directory)", src: dirSrc, - cfg: DefaultCreateSBOMConfig().WithPersistentCatalogers(newDummyCataloger("persistent")), + cfg: DefaultCreateSBOMConfig().WithCatalogers(newDummyCataloger("persistent")), wantTaskNames: [][]string{ environmentCatalogerNames(), addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"), @@ -227,9 +221,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"directory"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"directory"}, }, Used: addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"), }, @@ -238,7 +231,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { { name: "user-provided persistent cataloger is always run (user selection does not affect this)", src: imgSrc, - cfg: DefaultCreateSBOMConfig().WithPersistentCatalogers(newDummyCataloger("persistent")).WithCatalogerSelection("javascript"), + cfg: DefaultCreateSBOMConfig().WithCatalogers(newDummyCataloger("persistent")).WithCatalogerSelection(pkgcataloging.NewSelectionRequest().WithSubSelections("javascript")), wantTaskNames: [][]string{ environmentCatalogerNames(), addTo(pkgIntersect("image", "javascript"), "persistent"), @@ -246,9 +239,9 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{"javascript"}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, + SubSelectTags: []string{"javascript"}, }, Used: addTo(pkgIntersect("image", "javascript"), "persistent"), }, @@ -265,9 +258,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"), }, @@ -284,9 +276,8 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { relationshipCatalogerNames(), }, wantManifest: &catalogerManifest{ - Requested: task.SelectionRequest{ - Default: []string{"image"}, - Selection: []string{}, + Requested: pkgcataloging.SelectionRequest{ + DefaultNamesOrTags: []string{"image"}, }, Used: pkgCatalogerNamesWithTagOrName(t, "image"), }, @@ -325,7 +316,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) { t.Errorf("mismatched task group names (-want +got):\n%s", d) } - if d := cmp.Diff(tt.wantManifest, gotManifest, cmpopts.IgnoreFields(task.SelectionRequest{}, "Expressions")); d != "" { + if d := cmp.Diff(tt.wantManifest, gotManifest); d != "" { t.Errorf("mismatched cataloger manifest (-want +got):\n%s", d) } }) @@ -413,11 +404,6 @@ func Test_replaceDefaultTagReferences(t *testing.T) { lst: []string{"foo", "default", "bar"}, want: []string{"foo", "replacement", "bar"}, }, - { - name: "replace default tag with prefix", - lst: []string{"foo", "+default", "-default"}, - want: []string{"foo", "+replacement", "-replacement"}, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -439,21 +425,21 @@ func Test_findDefaultTag(t *testing.T) { src: source.Description{ Metadata: source.StereoscopeImageSourceMetadata{}, }, - want: task.ImageTag, + want: pkgcataloging.ImageTag, }, { name: "directory", src: source.Description{ Metadata: source.DirectorySourceMetadata{}, }, - want: task.DirectoryTag, + want: pkgcataloging.DirectoryTag, }, { name: "file", src: source.Description{ Metadata: source.FileSourceMetadata{}, }, - want: task.DirectoryTag, // not a mistake... + want: pkgcataloging.DirectoryTag, // not a mistake... }, { name: "unknown", diff --git a/test/integration/utils_test.go b/test/integration/utils_test.go index 1021203572b..8c2145016c7 100644 --- a/test/integration/utils_test.go +++ b/test/integration/utils_test.go @@ -9,6 +9,7 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/cmd/syft/cli/options" "github.com/anchore/syft/syft" + "github.com/anchore/syft/syft/cataloging/pkgcataloging" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" ) @@ -34,7 +35,11 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{ Name: "syft-tester", Version: "v0.99.0", - }).WithDefaultCatalogers("image").WithCatalogerSelection(catalogerSelection...) + }).WithCatalogerSelection( + pkgcataloging.NewSelectionRequest(). + WithDefaults(pkgcataloging.ImageTag). + WithExpression(catalogerSelection...), + ) cfg.Search.Scope = scope s, err := syft.CreateSBOM(theSource, cfg) @@ -61,7 +66,11 @@ func catalogDirectory(t *testing.T, dir string, catalogerSelection ...string) (s cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{ Name: "syft-tester", Version: "v0.99.0", - }).WithDefaultCatalogers("directory").WithCatalogerSelection(catalogerSelection...) + }).WithCatalogerSelection( + pkgcataloging.NewSelectionRequest(). + WithDefaults(pkgcataloging.DirectoryTag). + WithExpression(catalogerSelection...), + ) s, err := syft.CreateSBOM(theSource, cfg) require.NoError(t, err)