mirror of
https://github.com/anchore/syft
synced 2024-09-20 06:01:53 +00:00
b0ab75fd89
* remove existing cataloging API Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add file cataloging config Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add package cataloging config Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add configs for cross-cutting concerns Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename CLI option configs to not require import aliases later Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update all nested structs for the Catalog struct Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update Catalog cli options - add new cataloger selection options (selection and default) - remove the excludeBinaryOverlapByOwnership - deprecate "catalogers" flag - add new javascript configuration Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * migrate relationship capabilities to separate internal package Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor golang cataloger to use configuration options when creating packages Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * create internal object to facilitate reading from and writing to an SBOM Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * create a command-like object (task) to facilitate partial SBOM creation Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add cataloger selection capability - be able to parse string expressions into a set of resolved actions against sets - be able to use expressions to select/add/remove tasks to/from the final set of tasks to run Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add package, file, and environment related tasks Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update existing file catalogers to use nested UI elements Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add CreateSBOMConfig that drives the SBOM creation process Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * capture SBOM creation info as a struct Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add CreateSBOM() function Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update docs with SBOM selection help + breaking changes Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix multiple override default inputs Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix deprecation flag printing to stdout Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor cataloger selection description to separate object Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address review comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep expression errors and show specific suggestions only Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address additional review feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address more review comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * addressed additional PR review feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix file selection references Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove guess language data generation option Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add tests for coordinatesForSelection Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename relationship attributes Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add descriptions to relationships config fields Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * improve documentation around configuration options Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add explicit errors around legacy config entries Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
238 lines
6 KiB
Go
238 lines
6 KiB
Go
package task
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
|
|
"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-+]*)+$`)
|
|
|
|
const (
|
|
SetOperation Operation = "set"
|
|
AddOperation Operation = "add"
|
|
SubSelectOperation Operation = "sub-select"
|
|
RemoveOperation Operation = "remove"
|
|
)
|
|
|
|
var (
|
|
ErrEmptyToken = fmt.Errorf("no value given")
|
|
ErrInvalidToken = fmt.Errorf("invalid token given: only alphanumeric characters and hyphens are allowed")
|
|
ErrInvalidOperator = fmt.Errorf("invalid operator given")
|
|
ErrUnknownNameOrTag = fmt.Errorf("unknown name or tag given")
|
|
ErrTagsNotAllowed = fmt.Errorf("tags are not allowed with this operation (must use exact names)")
|
|
ErrNamesNotAllowed = fmt.Errorf("names are not allowed with this operation (must use tags)")
|
|
ErrAllNotAllowed = fmt.Errorf("cannot use the 'all' operand in this context")
|
|
)
|
|
|
|
// ErrInvalidExpression represents an expression that cannot be parsed or can be parsed but is logically invalid.
|
|
type ErrInvalidExpression struct {
|
|
Expression string
|
|
Operation Operation
|
|
Err error
|
|
}
|
|
|
|
func (e ErrInvalidExpression) Error() string {
|
|
return fmt.Sprintf("invalid expression: %q: %s", e.Expression, e.Err.Error())
|
|
}
|
|
|
|
func newErrInvalidExpression(exp string, op Operation, err error) ErrInvalidExpression {
|
|
return ErrInvalidExpression{
|
|
Expression: exp,
|
|
Operation: op,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
// Expression represents a single operation-operand pair with (all validation errors).
|
|
// E.g. "+foo", "-bar", or "something" are all expressions. Some validations are relevant to not only the
|
|
// syntax (operation and operator) but other are sensitive to the context of the operand (e.g. if a given operand
|
|
// is a tag or a name, validated against the operation).
|
|
type Expression struct {
|
|
Operation Operation
|
|
Operand string
|
|
Errors []error
|
|
}
|
|
|
|
// Operation represents the type of operation to perform on the operand (set, add, remove, sub-select).
|
|
type Operation string
|
|
|
|
// Expressions represents a list of expressions.
|
|
type Expressions []Expression
|
|
|
|
// expressionContext represents all information needed to validate an expression (e.g. the set of all tasks and their tags).
|
|
type expressionContext struct {
|
|
Names *strset.Set
|
|
Tags *strset.Set
|
|
}
|
|
|
|
func newExpressionContext(ts []Task) *expressionContext {
|
|
ec := &expressionContext{
|
|
Names: strset.New(tasks(ts).Names()...),
|
|
Tags: strset.New(tasks(ts).Tags()...),
|
|
}
|
|
|
|
ec.Tags.Add("all")
|
|
|
|
return ec
|
|
}
|
|
|
|
// 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:
|
|
// names and tags allowed
|
|
if !ec.Tags.Has(token) && !ec.Names.Has(token) {
|
|
err = newErrInvalidExpression(exp, operation, ErrUnknownNameOrTag)
|
|
}
|
|
case AddOperation:
|
|
// only names are allowed
|
|
if !ec.Names.Has(token) {
|
|
if ec.Tags.Has(token) {
|
|
err = newErrInvalidExpression(exp, operation, ErrTagsNotAllowed)
|
|
} else {
|
|
err = newErrInvalidExpression(exp, operation, ErrUnknownNameOrTag)
|
|
}
|
|
}
|
|
case SubSelectOperation:
|
|
if token == "all" {
|
|
// special case: we cannot sub-select all (this is most likely a misconfiguration and the user intended to use the set operation)
|
|
err = newErrInvalidExpression(exp, operation, ErrAllNotAllowed)
|
|
} else if !ec.Tags.Has(token) {
|
|
// only tags are allowed...
|
|
if ec.Names.Has(token) {
|
|
err = newErrInvalidExpression(exp, operation, ErrNamesNotAllowed)
|
|
} else {
|
|
err = newErrInvalidExpression(exp, operation, ErrUnknownNameOrTag)
|
|
}
|
|
}
|
|
}
|
|
|
|
var errs []error
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
return Expression{
|
|
Operation: operation,
|
|
Operand: token,
|
|
Errors: errs,
|
|
}
|
|
}
|
|
|
|
func newExpressionsFromSelectionRequest(nc *expressionContext, selectionRequest pkgcataloging.SelectionRequest) Expressions {
|
|
var all Expressions
|
|
|
|
for _, exp := range selectionRequest.DefaultNamesOrTags {
|
|
all = append(all, nc.newExpression(exp, SetOperation, exp))
|
|
}
|
|
|
|
for _, exp := range selectionRequest.SubSelectTags {
|
|
all = append(all, nc.newExpression(exp, SubSelectOperation, exp))
|
|
}
|
|
|
|
for _, exp := range selectionRequest.AddNames {
|
|
all = append(all, nc.newExpression(exp, AddOperation, exp))
|
|
}
|
|
|
|
for _, exp := range selectionRequest.RemoveNamesOrTags {
|
|
all = append(all, nc.newExpression(exp, RemoveOperation, exp))
|
|
}
|
|
|
|
sort.Sort(all)
|
|
return all
|
|
}
|
|
|
|
func isValidNode(s string) bool {
|
|
return expressionNodePattern.Match([]byte(s))
|
|
}
|
|
|
|
func (e Expressions) Clone() Expressions {
|
|
clone := make(Expressions, len(e))
|
|
copy(clone, e)
|
|
return clone
|
|
}
|
|
|
|
func (e Expression) String() string {
|
|
var op string
|
|
switch e.Operation {
|
|
case AddOperation:
|
|
op = "+"
|
|
case RemoveOperation:
|
|
op = "-"
|
|
case SubSelectOperation:
|
|
op = ""
|
|
case SetOperation:
|
|
op = ""
|
|
default:
|
|
op = "?"
|
|
}
|
|
return op + e.Operand
|
|
}
|
|
|
|
func (e Expressions) Len() int {
|
|
return len(e)
|
|
}
|
|
|
|
func (e Expressions) Swap(i, j int) {
|
|
e[i], e[j] = e[j], e[i]
|
|
}
|
|
|
|
// order of operations
|
|
var orderOfOps = map[Operation]int{
|
|
SetOperation: 1,
|
|
SubSelectOperation: 2,
|
|
RemoveOperation: 3,
|
|
AddOperation: 4,
|
|
}
|
|
|
|
func (e Expressions) Less(i, j int) bool {
|
|
ooi := orderOfOps[e[i].Operation]
|
|
ooj := orderOfOps[e[j].Operation]
|
|
|
|
if ooi != ooj {
|
|
return ooi < ooj
|
|
}
|
|
|
|
return i < j
|
|
}
|
|
|
|
func (e Expressions) Errors() (errs []error) {
|
|
for _, n := range e {
|
|
if len(n.Errors) > 0 {
|
|
errs = append(errs, n.Errors...)
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (e Expressions) Validate() error {
|
|
errs := e.Errors()
|
|
if len(errs) == 0 {
|
|
return nil
|
|
}
|
|
var err error
|
|
return multierror.Append(err, e.Errors()...)
|
|
}
|