mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Add cataloger list command (#2366)
* add cataloger list command Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * chore: tidy go mod Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com> Co-authored-by: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
fb2b54a6dc
commit
313d9212b6
@ -86,6 +86,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
|
|||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
scanCmd,
|
scanCmd,
|
||||||
commands.Packages(app, scanCmd), // this is currently an alias for the scan command
|
commands.Packages(app, scanCmd), // this is currently an alias for the scan command
|
||||||
|
commands.Cataloger(app),
|
||||||
commands.Attest(app),
|
commands.Attest(app),
|
||||||
commands.Convert(app),
|
commands.Convert(app),
|
||||||
clio.VersionCommand(id),
|
clio.VersionCommand(id),
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
|
attestExample = ` {{.appName}} {{.command}} --output [FORMAT] alpine:latest defaults to using images from a Docker daemon. If Docker is not present, the image is pulled directly from the registry
|
||||||
`
|
`
|
||||||
attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp
|
attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp
|
||||||
attestHelp = attestExample + attestSchemeHelp
|
attestHelp = attestExample + attestSchemeHelp
|
||||||
@ -232,7 +232,7 @@ func attestCommand(sbomFilepath string, opts *attestOptions, userInput string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func predicateType(outputName string) string {
|
func predicateType(outputName string) string {
|
||||||
// Select Cosign predicate type based on defined output type
|
// select the Cosign predicate type based on defined output type
|
||||||
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
||||||
switch strings.ToLower(outputName) {
|
switch strings.ToLower(outputName) {
|
||||||
case "cyclonedx-json":
|
case "cyclonedx-json":
|
||||||
|
|||||||
20
cmd/syft/cli/commands/cataloger.go
Normal file
20
cmd/syft/cli/commands/cataloger.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Cataloger(app clio.Application) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "cataloger",
|
||||||
|
Short: "Show available catalogers and configuration",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(
|
||||||
|
CatalogerList(app),
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
267
cmd/syft/cli/commands/cataloger_list.go
Normal file
267
cmd/syft/cli/commands/cataloger_list.go
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
"github.com/scylladb/go-set/strset"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/syft/internal/bus"
|
||||||
|
"github.com/anchore/syft/internal/task"
|
||||||
|
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||||
|
)
|
||||||
|
|
||||||
|
type catalogerListOptions struct {
|
||||||
|
Output string `yaml:"output" json:"output" mapstructure:"output"`
|
||||||
|
DefaultCatalogers []string `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"`
|
||||||
|
SelectCatalogers []string `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"`
|
||||||
|
ShowHidden bool `yaml:"show-hidden" json:"show-hidden" mapstructure:"show-hidden"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *catalogerListOptions) AddFlags(flags clio.FlagSet) {
|
||||||
|
flags.StringVarP(&o.Output, "output", "o", "format to output the cataloger list (available: table, json)")
|
||||||
|
|
||||||
|
flags.StringArrayVarP(&o.DefaultCatalogers, "override-default-catalogers", "", "override the default catalogers with an expression")
|
||||||
|
|
||||||
|
flags.StringArrayVarP(&o.SelectCatalogers, "select-catalogers", "", "select catalogers with an expression")
|
||||||
|
|
||||||
|
flags.BoolVarP(&o.ShowHidden, "show-hidden", "s", "show catalogers that have been de-selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCatalogerListOptions() *catalogerListOptions {
|
||||||
|
return &catalogerListOptions{
|
||||||
|
DefaultCatalogers: []string{"all"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CatalogerList(app clio.Application) *cobra.Command {
|
||||||
|
opts := defaultCatalogerListOptions()
|
||||||
|
|
||||||
|
return app.SetupCommand(&cobra.Command{
|
||||||
|
Use: "list [OPTIONS]",
|
||||||
|
Short: "List available catalogers",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runCatalogerList(opts)
|
||||||
|
},
|
||||||
|
}, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCatalogerList(opts *catalogerListOptions) error {
|
||||||
|
factories := task.DefaultPackageTaskFactories()
|
||||||
|
allTasks, err := factories.Tasks(task.DefaultCatalogingFactoryConfig())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create cataloger tasks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := catalogerListReport(opts, allTasks)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to generate cataloger list report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.Report(report)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalogerListReport(opts *catalogerListOptions, allTasks []task.Task) (string, error) {
|
||||||
|
selectedTasks, selectionEvidence, err := task.Select(allTasks,
|
||||||
|
pkgcataloging.NewSelectionRequest().
|
||||||
|
WithDefaults(opts.DefaultCatalogers...).
|
||||||
|
WithExpression(opts.SelectCatalogers...),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to select catalogers: %w", err)
|
||||||
|
}
|
||||||
|
var report string
|
||||||
|
|
||||||
|
switch opts.Output {
|
||||||
|
case "json":
|
||||||
|
report, err = renderCatalogerListJSON(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||||
|
case "table", "":
|
||||||
|
if opts.ShowHidden {
|
||||||
|
report = renderCatalogerListTable(allTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||||
|
} else {
|
||||||
|
report = renderCatalogerListTable(selectedTasks, selectionEvidence, opts.DefaultCatalogers, opts.SelectCatalogers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to render cataloger list: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCatalogerListJSON(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) (string, error) {
|
||||||
|
type node struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
names, tagsByName := extractTaskInfo(tasks)
|
||||||
|
|
||||||
|
nodesByName := make(map[string]node)
|
||||||
|
|
||||||
|
for name := range tagsByName {
|
||||||
|
tagsSelected := selection.TokensByTask[name].SelectedOn.List()
|
||||||
|
|
||||||
|
if len(tagsSelected) == 1 && tagsSelected[0] == "all" {
|
||||||
|
tagsSelected = tagsByName[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(tagsSelected)
|
||||||
|
|
||||||
|
if tagsSelected == nil {
|
||||||
|
// ensure collections are not null
|
||||||
|
tagsSelected = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesByName[name] = node{
|
||||||
|
Name: name,
|
||||||
|
Tags: tagsSelected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type document struct {
|
||||||
|
DefaultSelection []string `json:"default"`
|
||||||
|
Selection []string `json:"selection"`
|
||||||
|
Catalogers []node `json:"catalogers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if selections == nil {
|
||||||
|
// ensure collections are not null
|
||||||
|
selections = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc := document{
|
||||||
|
DefaultSelection: defaultSelections,
|
||||||
|
Selection: selections,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
doc.Catalogers = append(doc.Catalogers, nodesByName[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
by, err := json.Marshal(doc)
|
||||||
|
|
||||||
|
return string(by), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderCatalogerListTable(tasks []task.Task, selection task.Selection, defaultSelections, selections []string) string {
|
||||||
|
t := table.NewWriter()
|
||||||
|
t.SetStyle(table.StyleLight)
|
||||||
|
t.AppendHeader(table.Row{"Cataloger", "Tags"})
|
||||||
|
|
||||||
|
names, tagsByName := extractTaskInfo(tasks)
|
||||||
|
|
||||||
|
rowsByName := make(map[string]table.Row)
|
||||||
|
|
||||||
|
for name, tags := range tagsByName {
|
||||||
|
rowsByName[name] = formatRow(name, tags, selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
t.AppendRow(rowsByName[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
report := t.Render()
|
||||||
|
|
||||||
|
if len(selections) > 0 {
|
||||||
|
header := "Selected by expressions:\n"
|
||||||
|
for _, expr := range selections {
|
||||||
|
header += fmt.Sprintf(" - %q\n", expr)
|
||||||
|
}
|
||||||
|
report = header + report
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(defaultSelections) > 0 {
|
||||||
|
header := "Default selections:\n"
|
||||||
|
for _, expr := range defaultSelections {
|
||||||
|
header += fmt.Sprintf(" - %q\n", expr)
|
||||||
|
}
|
||||||
|
report = header + report
|
||||||
|
}
|
||||||
|
|
||||||
|
return report
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRow(name string, tags []string, selection task.Selection) table.Row {
|
||||||
|
isIncluded := selection.Result.Has(name)
|
||||||
|
var selections *task.TokenSelection
|
||||||
|
if s, exists := selection.TokensByTask[name]; exists {
|
||||||
|
selections = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
var formattedTags []string
|
||||||
|
for _, tag := range tags {
|
||||||
|
formattedTags = append(formattedTags, formatToken(tag, selections, isIncluded))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagStr string
|
||||||
|
if isIncluded {
|
||||||
|
tagStr = strings.Join(formattedTags, ", ")
|
||||||
|
} else {
|
||||||
|
tagStr = strings.Join(formattedTags, grey.Render(", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: selection should keep warnings (non-selections) in struct
|
||||||
|
|
||||||
|
return table.Row{
|
||||||
|
formatToken(name, selections, isIncluded),
|
||||||
|
tagStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = lipgloss.NewStyle().Foreground(lipgloss.Color("10")) // hi green
|
||||||
|
grey = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) // dark grey
|
||||||
|
red = lipgloss.NewStyle().Foreground(lipgloss.Color("9")) // high red
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatToken(token string, selection *task.TokenSelection, included bool) string {
|
||||||
|
if included && selection != nil {
|
||||||
|
// format all tokens in selection in green
|
||||||
|
if selection.SelectedOn.Has(token) {
|
||||||
|
return green.Render(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// format all tokens in selection in red, all others in grey
|
||||||
|
if selection != nil && selection.DeselectedOn.Has(token) {
|
||||||
|
return red.Render(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return grey.Render(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTaskInfo(tasks []task.Task) ([]string, map[string][]string) {
|
||||||
|
tagsByName := make(map[string][]string)
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
for _, tsk := range tasks {
|
||||||
|
var tags []string
|
||||||
|
name := tsk.Name()
|
||||||
|
|
||||||
|
if s, ok := tsk.(task.Selector); ok {
|
||||||
|
set := strset.New(s.Selectors()...)
|
||||||
|
set.Remove(name)
|
||||||
|
tags = set.List()
|
||||||
|
sort.Strings(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagsByName[name] = tags
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
return names, tagsByName
|
||||||
|
}
|
||||||
201
cmd/syft/cli/commands/cataloger_list_test.go
Normal file
201
cmd/syft/cli/commands/cataloger_list_test.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/scylladb/go-set/strset"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/sbomsync"
|
||||||
|
"github.com/anchore/syft/internal/task"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ interface {
|
||||||
|
task.Task
|
||||||
|
task.Selector
|
||||||
|
} = (*dummyTask)(nil)
|
||||||
|
|
||||||
|
type dummyTask struct {
|
||||||
|
name string
|
||||||
|
selectors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyTask) HasAllSelectors(s ...string) bool {
|
||||||
|
return strset.New(d.selectors...).Has(s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyTask) Selectors() []string {
|
||||||
|
return d.selectors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyTask) Name() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyTask) Execute(_ context.Context, _ file.Resolver, _ sbomsync.Builder) error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTasks() []task.Task {
|
||||||
|
return []task.Task{
|
||||||
|
dummyTask{
|
||||||
|
name: "task1",
|
||||||
|
selectors: []string{"image", "a", "b", "1"},
|
||||||
|
},
|
||||||
|
dummyTask{
|
||||||
|
name: "task2",
|
||||||
|
selectors: []string{"image", "b", "c", "2"},
|
||||||
|
},
|
||||||
|
dummyTask{
|
||||||
|
name: "task3",
|
||||||
|
selectors: []string{"directory", "c", "d", "3"},
|
||||||
|
},
|
||||||
|
dummyTask{
|
||||||
|
name: "task4",
|
||||||
|
selectors: []string{"directory", "d", "e", "4"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_catalogerListReport(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
options *catalogerListOptions
|
||||||
|
want string
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no expressions, table",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "table"
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
Default selections:
|
||||||
|
- "all"
|
||||||
|
┌───────────┬────────────────────┐
|
||||||
|
│ CATALOGER │ TAGS │
|
||||||
|
├───────────┼────────────────────┤
|
||||||
|
│ task1 │ 1, a, b, image │
|
||||||
|
│ task2 │ 2, b, c, image │
|
||||||
|
│ task3 │ 3, c, d, directory │
|
||||||
|
│ task4 │ 4, d, directory, e │
|
||||||
|
└───────────┴────────────────────┘
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no expressions, json",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "json"
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
{"default":["all"],"selection":[],"catalogers":[{"name":"task1","tags":["1","a","b","image"]},{"name":"task2","tags":["2","b","c","image"]},{"name":"task3","tags":["3","c","d","directory"]},{"name":"task4","tags":["4","d","directory","e"]}]}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no expressions, default selection, table",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "table"
|
||||||
|
c.DefaultCatalogers = []string{
|
||||||
|
"image",
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
Default selections:
|
||||||
|
- "image"
|
||||||
|
┌───────────┬────────────────┐
|
||||||
|
│ CATALOGER │ TAGS │
|
||||||
|
├───────────┼────────────────┤
|
||||||
|
│ task1 │ 1, a, b, image │
|
||||||
|
│ task2 │ 2, b, c, image │
|
||||||
|
└───────────┴────────────────┘
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no expressions, default selection, json",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "json"
|
||||||
|
c.DefaultCatalogers = []string{
|
||||||
|
"image",
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
{"default":["image"],"selection":[],"catalogers":[{"name":"task1","tags":["image"]},{"name":"task2","tags":["image"]}]}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with expressions, default selection, table",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "table"
|
||||||
|
c.DefaultCatalogers = []string{
|
||||||
|
"image",
|
||||||
|
}
|
||||||
|
c.SelectCatalogers = []string{
|
||||||
|
"-directory",
|
||||||
|
"+task3",
|
||||||
|
"-c",
|
||||||
|
"b",
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
Default selections:
|
||||||
|
- "image"
|
||||||
|
Selected by expressions:
|
||||||
|
- "-directory"
|
||||||
|
- "+task3"
|
||||||
|
- "-c"
|
||||||
|
- "b"
|
||||||
|
┌───────────┬────────────────────┐
|
||||||
|
│ CATALOGER │ TAGS │
|
||||||
|
├───────────┼────────────────────┤
|
||||||
|
│ task1 │ 1, a, b, image │
|
||||||
|
│ task3 │ 3, c, d, directory │
|
||||||
|
└───────────┴────────────────────┘
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with expressions, default selection, json",
|
||||||
|
options: func() *catalogerListOptions {
|
||||||
|
c := defaultCatalogerListOptions()
|
||||||
|
c.Output = "json"
|
||||||
|
c.DefaultCatalogers = []string{
|
||||||
|
"image",
|
||||||
|
}
|
||||||
|
c.SelectCatalogers = []string{
|
||||||
|
"-directory",
|
||||||
|
"+task3",
|
||||||
|
"-c",
|
||||||
|
"b",
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
want: `
|
||||||
|
{"default":["image"],"selection":["-directory","+task3","-c","b"],"catalogers":[{"name":"task1","tags":["b","image"]},{"name":"task3","tags":["task3"]}]}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.wantErr == nil {
|
||||||
|
tt.wantErr = require.NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := catalogerListReport(tt.options, testTasks())
|
||||||
|
tt.wantErr(t, err)
|
||||||
|
assert.Equal(t, strings.TrimSpace(tt.want), strings.TrimSpace(got))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/clio"
|
"github.com/anchore/clio"
|
||||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
|
func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
|
||||||
@ -13,11 +14,14 @@ func Packages(app clio.Application, scanCmd *cobra.Command) *cobra.Command {
|
|||||||
opts := defaultScanOptions()
|
opts := defaultScanOptions()
|
||||||
|
|
||||||
cmd := app.SetupCommand(&cobra.Command{
|
cmd := app.SetupCommand(&cobra.Command{
|
||||||
Use: "packages [SOURCE]",
|
Use: "packages [SOURCE]",
|
||||||
Short: scanCmd.Short,
|
Short: scanCmd.Short,
|
||||||
Long: scanCmd.Long,
|
Long: scanCmd.Long,
|
||||||
Args: scanCmd.Args,
|
Args: scanCmd.Args,
|
||||||
Example: scanCmd.Example,
|
Example: internal.Tprintf(scanHelp, map[string]interface{}{
|
||||||
|
"appName": id.Name,
|
||||||
|
"command": "packages",
|
||||||
|
}),
|
||||||
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||||
|
|||||||
9
go.mod
9
go.mod
@ -17,10 +17,12 @@ require (
|
|||||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||||
github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54
|
github.com/anchore/stereoscope v0.0.0-20231220161148-590920dabc54
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||||
|
github.com/charmbracelet/bubbles v0.17.1
|
||||||
github.com/charmbracelet/bubbletea v0.25.0
|
github.com/charmbracelet/bubbletea v0.25.0
|
||||||
github.com/charmbracelet/lipgloss v0.9.1
|
github.com/charmbracelet/lipgloss v0.9.1
|
||||||
github.com/dave/jennifer v1.7.0
|
github.com/dave/jennifer v1.7.0
|
||||||
@ -42,6 +44,7 @@ require (
|
|||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/iancoleman/strcase v0.3.0
|
github.com/iancoleman/strcase v0.3.0
|
||||||
github.com/invopop/jsonschema v0.7.0
|
github.com/invopop/jsonschema v0.7.0
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.5.2
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953
|
||||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
|
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
|
||||||
@ -76,12 +79,6 @@ require (
|
|||||||
modernc.org/sqlite v1.28.0
|
modernc.org/sqlite v1.28.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
|
||||||
github.com/charmbracelet/bubbles v0.17.1
|
|
||||||
github.com/jedib0t/go-pretty/v6 v6.5.3
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -466,8 +466,8 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
|
|||||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
github.com/jedib0t/go-pretty/v6 v6.5.3 h1:GIXn6Er/anHTkVUoufs7ptEvxdD6KIhR7Axa2wYCPF0=
|
github.com/jedib0t/go-pretty/v6 v6.5.2 h1:1zphkAo77tdoCkdqIYsMHoXmEGTnTy3GZ6Mn+NyIro0=
|
||||||
github.com/jedib0t/go-pretty/v6 v6.5.3/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
|
github.com/jedib0t/go-pretty/v6 v6.5.2/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
|
||||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user