Add db list command (#506)

* add db list command

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add stderr print helper

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update docs to with details about listing files and DB curation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2021-12-03 11:43:50 -05:00 committed by GitHub
parent 2867dc0118
commit 86b7d165e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 114 additions and 20 deletions

View file

@ -272,7 +272,9 @@ You can set the cache directory path using the environment variable `GRYPE_DB_CA
By default, Grype checks for a new database on every run, by making a network call over the Internet. You can tell Grype not to perform this check by setting the environment variable `GRYPE_DB_AUTO_UPDATE` to `false`.
As long as you place Grype's `vulnerability.db` and `metadata.json` files in the cache directory for the expected schema version, Grype has no need to access the network.
As long as you place Grype's `vulnerability.db` and `metadata.json` files in the cache directory for the expected schema version, Grype has no need to access the network. Additionally, you can get a listing of the database archives available for download from the `grype db list` command in an online environment, download the database archive, transfer it to your offline environment, and use `grype db import <db-archive-path>` to use the given database in an offline capacity.
If you would like to distribute your own Grype databases internally without needing to use `db import` manually you can leverage Grype's DB update mechanism. To do this you can craft your own `listing.json` file similar to the one found publically (see `grype db list -o raw` for an example of our public `listing.json` file) and change the download URL to point to an internal endpoint (e.g. a private S3 bucket, an internal file server, etc). Any internal installation of Grype can receive database updates automatically by configuring the `db.update-url` (same as the `GRYPE_DB_UPDATE_URL` environment variable) to point to the hosted `listing.json` file you've crafted.
#### CLI commands for database management
@ -282,7 +284,11 @@ Grype provides database-specific CLI commands for users that want to control the
`grype db check` — see if updates are available for the database
`grype db update` — ensure the latest database has been downloaded to the cache directory (Grype performs this operation at the beginnign of every scan by default)
`grype db update` — ensure the latest database has been downloaded to the cache directory (Grype performs this operation at the beginning of every scan by default)
`grype db list` — download the listing file configured at `db.update-url` and show databases that are available for download
`grype db import` — provide grype with a database archive to explicitly use (useful for offline DB updates)
Find complete information on Grype's database commands by running `grype db --help`.

View file

@ -34,7 +34,7 @@ func init() {
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
_ = stderrPrintLnf(err.Error())
os.Exit(1)
}
}

View file

@ -26,15 +26,12 @@ func runDBCheckCmd(_ *cobra.Command, _ []string) error {
updateAvailable, _, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
return fmt.Errorf("unable to check for vulnerability database update: %+v", err)
}
if !updateAvailable {
fmt.Println("No update available")
return nil
return stderrPrintLnf("No update available")
}
fmt.Println("Update available!")
return nil
return stderrPrintLnf("Update available!")
}

View file

@ -28,6 +28,5 @@ func runDBDeleteCmd(_ *cobra.Command, _ []string) error {
return fmt.Errorf("unable to delete vulnerability database: %+v", err)
}
fmt.Println("Vulnerability database deleted")
return nil
return stderrPrintLnf("Vulnerability database deleted")
}

View file

@ -31,6 +31,5 @@ func runDBImportCmd(_ *cobra.Command, args []string) error {
return fmt.Errorf("unable to import vulnerability database: %+v", err)
}
fmt.Println("Vulnerability database imported")
return nil
return stderrPrintLnf("Vulnerability database imported")
}

76
cmd/db_list.go Normal file
View file

@ -0,0 +1,76 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/anchore/grype/grype/db"
"github.com/spf13/cobra"
)
var dbListOutputFormat string
var dbListCmd = &cobra.Command{
Use: "list",
Short: "list all DBs available according to the listing URL",
Args: cobra.ExactArgs(0),
RunE: runDBListCmd,
}
func init() {
dbListCmd.Flags().StringVarP(&dbListOutputFormat, "output", "o", "text", "format to display results (available=[text, raw, json])")
dbCmd.AddCommand(dbListCmd)
}
func runDBListCmd(_ *cobra.Command, _ []string) error {
dbCurator, err := db.NewCurator(appConfig.DB.ToCuratorConfig())
if err != nil {
return err
}
listing, err := dbCurator.ListingFromURL()
if err != nil {
return err
}
supportedSchema := dbCurator.SupportedSchema()
available, exists := listing.Available[supportedSchema]
if len(available) == 0 || !exists {
return stderrPrintLnf("No databases available for the current schema (%d)", supportedSchema)
}
switch dbListOutputFormat {
case "text":
// summarize each listing entry for the current DB schema
for _, l := range available {
fmt.Printf("Built: %s\n", l.Built)
fmt.Printf("URL: %s\n", l.URL)
fmt.Printf("Checksum: %s\n\n", l.Checksum)
}
fmt.Printf("%d databases available for schema %d\n", len(available), supportedSchema)
case "json":
// show entries for the current schema
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(&available); err != nil {
return fmt.Errorf("failed to db listing information: %+v", err)
}
case "raw":
// show the entire listing file
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
if err := enc.Encode(&listing); err != nil {
return fmt.Errorf("failed to db listing information: %+v", err)
}
default:
return fmt.Errorf("unsupported output format: %s", dbListOutputFormat)
}
return nil
}

View file

@ -31,10 +31,8 @@ func runDBUpdateCmd(_ *cobra.Command, _ []string) error {
}
if updated {
fmt.Println("Vulnerability database updated!")
return nil
return stderrPrintLnf("Vulnerability database updated!")
}
fmt.Println("No vulnerability database update available")
return nil
return stderrPrintLnf("No vulnerability database update available")
}

15
cmd/util.go Normal file
View file

@ -0,0 +1,15 @@
package cmd
import (
"fmt"
"os"
"strings"
)
func stderrPrintLnf(message string, args ...interface{}) error {
if !strings.HasSuffix(message, "\n") {
message += "\n"
}
_, err := fmt.Fprintf(os.Stderr, message, args...)
return err
}

View file

@ -11,7 +11,7 @@ import (
"github.com/spf13/cobra"
)
var outputFormat string
var versionOutputFormat string
var versionCmd = &cobra.Command{
Use: "version",
@ -20,14 +20,14 @@ var versionCmd = &cobra.Command{
}
func init() {
versionCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "format to show version information (available=[text, json])")
versionCmd.Flags().StringVarP(&versionOutputFormat, "output", "o", "text", "format to display results (available=[text, json])")
rootCmd.AddCommand(versionCmd)
}
func printVersion(_ *cobra.Command, _ []string) error {
versionInfo := version.FromBuild()
switch outputFormat {
switch versionOutputFormat {
case "text":
fmt.Println("Application: ", internal.ApplicationName)
fmt.Println("Version: ", versionInfo.Version)
@ -57,7 +57,7 @@ func printVersion(_ *cobra.Command, _ []string) error {
return fmt.Errorf("failed to show version information: %+v", err)
}
default:
return fmt.Errorf("unsupported output format: %s", outputFormat)
return fmt.Errorf("unsupported output format: %s", versionOutputFormat)
}
return nil
}

View file

@ -65,6 +65,10 @@ func NewCurator(cfg Config) (Curator, error) {
}, nil
}
func (c Curator) SupportedSchema() int {
return c.targetSchema
}
func (c *Curator) GetStore() (*reader.Reader, error) {
// ensure the DB is ok
err := c.Validate()