Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add db search subcommand #2031

Merged
merged 16 commits into from
Aug 12, 2024
1 change: 1 addition & 0 deletions cmd/grype/cli/commands/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func DB(app clio.Application) *cobra.Command {
DBList(app),
DBStatus(app),
DBUpdate(app),
DBSearch(app),
)

return db
Expand Down
116 changes: 116 additions & 0 deletions cmd/grype/cli/commands/db_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package commands

import (
"encoding/json"
"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"
)

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

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

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: "table",
DBOptions: *dbOptionsDefault(app.ID()),
}

return app.SetupCommand(&cobra.Command{
Use: "search [vulnerability_id]",
Short: "get information on a vulnerability from the db",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) (err error) {
id := args[0]
return runDBSearch(opts, id)
},
}, opts)
}

func runDBSearch(opts *dbQueryOptions, vulnerabilityID string) error {
log.Debug("loading DB")
str, status, dbCloser, err := grype.LoadVulnerabilityDB(opts.DB.ToCuratorConfig(), opts.DB.AutoUpdate)
err = validateDBLoad(err, status)
if err != nil {
return err
}
if dbCloser != nil {
defer dbCloser.Close()
}

vulnerabilities, err := str.Get(vulnerabilityID, "")
if err != nil {
return err
}

sb := &strings.Builder{}
if len(vulnerabilities) == 0 {
return fmt.Errorf("vulnerability doesn't exist in the DB: %s", vulnerabilityID)
}

err = present(opts.Output, vulnerabilities, sb)
bus.Report(sb.String())

return err
}

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

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

table := tablewriter.NewWriter(output)
columns := []string{"ID", "Package Name", "Namespace", "Version Constraint"}

table.SetHeader(columns)
table.SetAutoWrapText(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)

table.SetHeaderLine(false)
table.SetBorder(false)
table.SetAutoFormatHeaders(true)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetTablePadding(" ")
table.SetNoWhiteSpace(true)

table.AppendBulk(rows)
table.Render()
case "json":
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
}
8 changes: 7 additions & 1 deletion grype/db/v5/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,13 @@ func (s *store) GetVulnerabilityNamespaces() ([]string, error) {
func (s *store) GetVulnerability(namespace, id string) ([]v5.Vulnerability, error) {
var models []model.VulnerabilityModel

result := s.db.Where("namespace = ? AND id = ?", namespace, id).Find(&models)
query := s.db.Where("id = ?", id)

if namespace != "" {
query = query.Where("namespace = ?", namespace)
}

result := query.Find(&models)

var vulnerabilities = make([]v5.Vulnerability, len(models))
for idx, m := range models {
Expand Down
1 change: 1 addition & 0 deletions grype/db/vulnerability_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func (pr *VulnerabilityProvider) Get(id, namespace string) ([]vulnerability.Vuln
var results []vulnerability.Vulnerability
for _, vuln := range vulns {
vulnObj, err := vulnerability.NewVulnerability(vuln)
vulnObj.PackageName = vuln.PackageName
kzantow marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("provider failed to inflate vulnerability record (namespace=%q id=%q): %w", vuln.Namespace, vuln.ID, err)
}
Expand Down
1 change: 1 addition & 0 deletions grype/db/vulnerability_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ func Test_Get(t *testing.T) {
expected := []vulnerability.Vulnerability{
{
Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
PackageName: "neutron",
ID: "CVE-2014-fake-1",
Namespace: "debian:distro:debian:8",
PackageQualifiers: []qualifier.Qualifier{},
Expand Down
1 change: 1 addition & 0 deletions grype/vulnerability/vulnerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Reference struct {
}

type Vulnerability struct {
PackageName string
Constraint version.Constraint
PackageQualifiers []qualifier.Qualifier
CPEs []cpe.CPE
Expand Down
Loading