Skip to content

Commit

Permalink
preserve v5 search command
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Dec 17, 2024
1 parent f2baf2b commit 9c40131
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 94 deletions.
114 changes: 111 additions & 3 deletions cmd/grype/cli/commands/db_search.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,129 @@
package commands

import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grype/grype"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
)

var _ clio.FlagAdder = (*dbQueryOptions)(nil)

type dbQueryOptions struct {
Output string `yaml:"output" json:"output" mapstructure:"output"`
DBOptions `yaml:",inline" mapstructure:",squash"`
}

func (c *dbQueryOptions) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&c.Output, "output", "o", "format to display results (available=[table, json])")
}

func DBSearch(app clio.Application) *cobra.Command {
opts := &dbQueryOptions{
Output: tableOutputFormat,
DBOptions: *dbOptionsDefault(app.ID()),
}

cmd := &cobra.Command{
Use: "search",
Short: "search the DB for vulnerabilities or affected packages",
// this is here to support v5 functionality today but will be removed when v6 is the default DB version
Use: "search ID...",
Short: "get information on vulnerabilities from the db",
//Use: "search",
//Short: "search the DB for vulnerabilities or affected packages",
PreRunE: disableUI(app),
RunE: func(cmd *cobra.Command, args []string) (err error) {
if opts.Experimental.DBv6 {
if len(args) > 0 {
// looks like the user attempted to use the search command as if it's v5 -- let them know about the new commands instead
return errors.New("this command is only supported for schema DB v5, please use `grype db search pkg` or `grype db search vuln` for schema DB v6+")
}
// running without args should only show help, not as a runtime error
return cmd.Usage()
}

// this is v5, do arg handling here. Why not do this earlier in the struct Args field? When v6 functionality is
// enabled we want this command to show usage and exit, so we need to do this check later in processing (here).
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
return legacyDBSearchPackages(*opts, args)
},
}

cmd.AddCommand(
DBSearchPackages(app),
DBSearchVulnerabilities(app),
)

return cmd
return app.SetupCommand(cmd, opts)
}

func legacyDBSearchPackages(opts dbQueryOptions, vulnerabilityIDs []string) error {
log.Debug("loading DB")
str, status, err := grype.LoadVulnerabilityDB(opts.DB.ToLegacyCuratorConfig(), opts.DB.AutoUpdate)
err = validateDBLoad(err, status)
if err != nil {
return err
}
defer log.CloseAndLogError(str, status.Location)

var vulnerabilities []vulnerability.Vulnerability
for _, vulnerabilityID := range vulnerabilityIDs {
vulns, err := str.Get(vulnerabilityID, "")
if err != nil {
return fmt.Errorf("unable to get vulnerability %q: %w", vulnerabilityID, err)
}
vulnerabilities = append(vulnerabilities, vulns...)
}

if len(vulnerabilities) == 0 {
return errors.New("no affected packages found")
}

sb := &strings.Builder{}
err = presentLegacyDBSearchPackages(opts.Output, vulnerabilities, sb)
bus.Report(sb.String())

return err
}

func presentLegacyDBSearchPackages(outputFormat string, vulnerabilities []vulnerability.Vulnerability, output io.Writer) error {
if vulnerabilities == nil {
return nil
}

switch outputFormat {
case tableOutputFormat:
rows := [][]string{}
for _, v := range vulnerabilities {
rows = append(rows, []string{v.ID, v.PackageName, v.Namespace, v.Constraint.String()})
}

table := tablewriter.NewWriter(output)
commonTableWriterOptions(table)

table.SetHeader([]string{"ID", "Package Name", "Namespace", "Version Constraint"})
table.AppendBulk(rows)
table.Render()
case jsonOutputFormat:
enc := json.NewEncoder(output)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(vulnerabilities); err != nil {
return fmt.Errorf("failed to encode diff information: %+v", err)
}
default:
return fmt.Errorf("unsupported output format: %s", outputFormat)
}
return nil
}
80 changes: 4 additions & 76 deletions cmd/grype/cli/commands/db_search_pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ import (
"github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/commands/internal/dbsearch"
"github.com/anchore/grype/cmd/grype/cli/options"
"github.com/anchore/grype/grype"
"github.com/anchore/grype/grype/db/v6/distribution"
"github.com/anchore/grype/grype/db/v6/installation"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
)

type dbSearchPackageOptions struct {
Expand Down Expand Up @@ -48,25 +45,21 @@ func DBSearchPackages(app clio.Application) *cobra.Command {
return app.SetupCommand(&cobra.Command{
Use: "pkg PURL|CPE|NAME...",
Aliases: []string{"package", "packages", "pkgs"},
Short: "Search for packages affected by vulnerabilities within the db",
Short: "Search for packages affected by vulnerabilities within the db (supports DB schema v6+ only)",
Args: func(_ *cobra.Command, args []string) error {
opts.Package.Names = args
return nil
},
RunE: func(_ *cobra.Command, _ []string) (err error) {
if !opts.Experimental.DBv6 {
return errors.New("this command only supports the v6+ database schemas")
}
return runDBSearchPackages(*opts)
},
}, opts)
}

func runDBSearchPackages(opts dbSearchPackageOptions) error {
if opts.Experimental.DBv6 {
return newDBSearchPackages(opts)
}
return legacyDBSearchPackages(opts)
}

func newDBSearchPackages(opts dbSearchPackageOptions) error {
client, err := distribution.NewClient(opts.DB.ToClientConfig())
if err != nil {
return fmt.Errorf("unable to create distribution client: %w", err)
Expand Down Expand Up @@ -161,68 +154,3 @@ func renderDBSearchPackagesTableRows(structuredRows []dbsearch.AffectedPackageTa
}
return rows
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// all legacy processing below ////////////////////////////////////////////////////////////////////////////////////////

func legacyDBSearchPackages(opts dbSearchPackageOptions) error {
vulnerabilityIDs := opts.Vulnerability.VulnerabilityIDs

log.Debug("loading DB")
str, status, err := grype.LoadVulnerabilityDB(opts.DB.ToLegacyCuratorConfig(), opts.DB.AutoUpdate)
err = validateDBLoad(err, status)
if err != nil {
return err
}
defer log.CloseAndLogError(str, status.Location)

var vulnerabilities []vulnerability.Vulnerability
for _, vulnerabilityID := range vulnerabilityIDs {
vulns, err := str.Get(vulnerabilityID, "")
if err != nil {
return fmt.Errorf("unable to get vulnerability %q: %w", vulnerabilityID, err)
}
vulnerabilities = append(vulnerabilities, vulns...)
}

if len(vulnerabilities) == 0 {
return errors.New("no affected packages found")
}

sb := &strings.Builder{}
err = presentLegacyDBSearchPackages(opts.Format.Output, vulnerabilities, sb)
bus.Report(sb.String())

return err
}

func presentLegacyDBSearchPackages(outputFormat string, vulnerabilities []vulnerability.Vulnerability, output io.Writer) error {
if vulnerabilities == nil {
return nil
}

switch outputFormat {
case tableOutputFormat:
rows := [][]string{}
for _, v := range vulnerabilities {
rows = append(rows, []string{v.ID, v.PackageName, v.Namespace, v.Constraint.String()})
}

table := tablewriter.NewWriter(output)
commonTableWriterOptions(table)

table.SetHeader([]string{"ID", "Package Name", "Namespace", "Version Constraint"})
table.AppendBulk(rows)
table.Render()
case jsonOutputFormat:
enc := json.NewEncoder(output)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(vulnerabilities); err != nil {
return fmt.Errorf("failed to encode diff information: %+v", err)
}
default:
return fmt.Errorf("unsupported output format: %s", outputFormat)
}
return nil
}
12 changes: 4 additions & 8 deletions cmd/grype/cli/commands/db_search_vuln.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func DBSearchVulnerabilities(app clio.Application) *cobra.Command {
return app.SetupCommand(&cobra.Command{
Use: "vuln ID...",
Aliases: []string{"vulnerability", "vulnerabilities", "vulns"},
Short: "Search for vulnerabilities within the DB",
Short: "Search for vulnerabilities within the DB (supports DB schema v6+ only)",
Args: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("must specify at least one vulnerability ID")
Expand All @@ -56,19 +56,15 @@ func DBSearchVulnerabilities(app clio.Application) *cobra.Command {
return nil
},
RunE: func(_ *cobra.Command, _ []string) (err error) {
if !opts.Experimental.DBv6 {
return errors.New("this command only supports the v6+ database schemas")
}
return runDBSearchVulnerabilities(*opts)
},
}, opts)
}

func runDBSearchVulnerabilities(opts dbSearchVulnerabilityOptions) error {
if opts.Experimental.DBv6 {
return runNewDBSearchVulnerabilities(opts)
}
return errors.New("this command only supports the v6+ database schemas")
}

func runNewDBSearchVulnerabilities(opts dbSearchVulnerabilityOptions) error {
client, err := distribution.NewClient(opts.DB.ToClientConfig())
if err != nil {
return fmt.Errorf("unable to create distribution client: %w", err)
Expand Down
3 changes: 2 additions & 1 deletion cmd/grype/cli/options/database_search_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type DBSearchFormat struct {
}

func (c *DBSearchFormat) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&c.Output, "output", "o", "format to display results (available=[table, json])")
available := strings.Join(c.Allowable, ", ")
flags.StringVarP(&c.Output, "output", "o", fmt.Sprintf("format to display results (available=[%s])", available))
}

func (c *DBSearchFormat) PostLoad() error {
Expand Down
3 changes: 2 additions & 1 deletion cmd/grype/cli/options/database_search_os.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type DBSearchOSs struct {
}

func (o *DBSearchOSs) AddFlags(flags clio.FlagSet) {
flags.StringArrayVarP(&o.OSs, "distro", "", "refine to results with the given operating system (format: 'name', 'name@version', '[email protected]', 'name@codename') (for v6+ schemas only)") // consistent with grype flag today
// consistent with grype --distro flag today
flags.StringArrayVarP(&o.OSs, "distro", "", "refine to results with the given operating system (format: 'name', 'name@version', '[email protected]', 'name@codename')")
}

func (o *DBSearchOSs) PostLoad() error {
Expand Down
2 changes: 1 addition & 1 deletion cmd/grype/cli/options/database_search_packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type DBSearchPackages struct {
}

func (o *DBSearchPackages) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&o.Ecosystem, "ecosystem", "", "ecosystem of the package to search within (for v6+ schemas only)")
flags.StringVarP(&o.Ecosystem, "ecosystem", "", "ecosystem of the package to search within")
}

func (o *DBSearchPackages) PostLoad() error {
Expand Down
8 changes: 4 additions & 4 deletions cmd/grype/cli/options/database_search_vulnerabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ type DBSearchVulnerabilities struct {

func (c *DBSearchVulnerabilities) AddFlags(flags clio.FlagSet) {
if c.UseVulnIDFlag {
flags.StringArrayVarP(&c.VulnerabilityIDs, "vuln", "", "only show results for the given vulnerability ID (for v6+ schemas only)")
flags.StringArrayVarP(&c.VulnerabilityIDs, "vuln", "", "only show results for the given vulnerability ID")
}
flags.BoolVarP(&c.IncludeAliases, "include-aliases", "", "search for vulnerability aliases (for v6+ schemas only)")
flags.StringVarP(&c.PublishedAfter, "published-after", "", "only show vulnerabilities originally published after the given date (format: YYYY-MM-DD) (for v6+ schemas only)")
flags.StringVarP(&c.ModifiedAfter, "modified-after", "", "only show vulnerabilities originally published or modified since the given date (format: YYYY-MM-DD) (for v6+ schemas only)")
flags.StringArrayVarP(&c.Providers, "provider", "", "only show vulnerabilities from the given provider (for v6+ schemas only)")
flags.StringVarP(&c.PublishedAfter, "published-after", "", "only show vulnerabilities originally published after the given date (format: YYYY-MM-DD)")
flags.StringVarP(&c.ModifiedAfter, "modified-after", "", "only show vulnerabilities originally published or modified since the given date (format: YYYY-MM-DD)")
flags.StringArrayVarP(&c.Providers, "provider", "", "only show vulnerabilities from the given provider")
}

func (c *DBSearchVulnerabilities) PostLoad() error {
Expand Down

0 comments on commit 9c40131

Please sign in to comment.