Skip to content

Commit

Permalink
chore: update CLI to CLIO (#1437)
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Zantow <kzantow@gmail.com>
  • Loading branch information
kzantow authored Sep 11, 2023
1 parent 1772f25 commit 02d513e
Show file tree
Hide file tree
Showing 75 changed files with 1,417 additions and 2,319 deletions.
8 changes: 4 additions & 4 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ builds:
-w
-s
-extldflags '-static'
-X github.com/anchore/grype/internal/version.version={{.Version}}
-X github.com/anchore/grype/internal/version.gitCommit={{.Commit}}
-X github.com/anchore/grype/internal/version.buildDate={{.Date}}
-X github.com/anchore/grype/internal/version.gitDescription={{.Summary}}
-X main.version={{.Version}}
-X main.gitCommit={{.Commit}}
-X main.buildDate={{.Date}}
-X main.gitDescription={{.Summary}}

- id: darwin-build
dir: ./cmd/grype
Expand Down
99 changes: 99 additions & 0 deletions cmd/grype/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package cli

import (
"os"
"runtime/debug"

"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/commands"
handler "github.com/anchore/grype/cmd/grype/cli/ui"
"github.com/anchore/grype/cmd/grype/internal/ui"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/redact"
"github.com/anchore/stereoscope"
"github.com/anchore/syft/syft"
)

func Application(id clio.Identification) clio.Application {
app, _ := create(id)
return app
}

func Command(id clio.Identification) *cobra.Command {
_, cmd := create(id)
return cmd
}

func create(id clio.Identification) (clio.Application, *cobra.Command) {
clioCfg := clio.NewSetupConfig(id).
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
WithUIConstructor(
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
func(cfg clio.Config) ([]clio.UI, error) {
noUI := ui.None(cfg.Log.Quiet)
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
return []clio.UI{noUI}, nil
}

h := handler.New(handler.DefaultHandlerConfig())

return []clio.UI{
ui.New(cfg.Log.Quiet, h),
noUI,
}, nil
},
).
WithInitializers(
func(state *clio.State) error {
// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
// we can hoist them into the internal packages for global use.
stereoscope.SetBus(state.Bus)
syft.SetBus(state.Bus)
bus.Set(state.Bus)

redact.Set(state.RedactStore)

stereoscope.SetLogger(state.Logger)
syft.SetLogger(state.Logger)
log.Set(state.Logger)

return nil
},
)

app := clio.New(*clioCfg)

rootCmd := commands.Root(app)

// add sub-commands
rootCmd.AddCommand(
commands.DB(app),
commands.Completion(),
commands.Explain(app),
clio.VersionCommand(id, syftVersion),
)

return app, rootCmd
}

func syftVersion() (string, string) {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
log.Debug("unable to find the buildinfo section of the binary (syft version is unknown)")
return "", ""
}

for _, d := range buildInfo.Deps {
if d.Path == "github.com/anchore/syft" {
return "SyftVersion", d.Version
}
}

log.Debug("unable to find 'github.com/anchore/syft' from the buildinfo section of the binary")
return "", ""
}
19 changes: 19 additions & 0 deletions cmd/grype/cli/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cli

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/anchore/clio"
)

func Test_Command(t *testing.T) {
root := Command(clio.Identification{
Name: "test-name",
Version: "test-version",
})

require.Equal(t, root.Name(), "test-name")
require.NotEmpty(t, root.Commands())
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package legacy
package commands

import (
"context"
Expand All @@ -11,11 +11,12 @@ import (
"github.com/spf13/cobra"
)

// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish]",
Short: "Generate a shell completion for Grype (listing local docker images)",
Long: `To load completions (docker image list):
// Completion returns a command to provide completion to various terminal shells
func Completion() *cobra.Command {
return &cobra.Command{
Use: "completion [bash|zsh|fish]",
Short: "Generate a shell completion for Grype (listing local docker images)",
Long: `To load completions (docker image list):
Bash:
Expand Down Expand Up @@ -46,40 +47,22 @@ $ grype completion fish | source
# To load completions for each session, execute once:
$ grype completion fish > ~/.config/fish/completions/grype.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "fish", "zsh"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
switch args[0] {
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
}
return err
},
}

func init() {
rootCmd.AddCommand(completionCmd)
}

func dockerImageValidArgsFunction(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Since we use ValidArgsFunction, Cobra will call this AFTER having parsed all flags and arguments provided
dockerImageRepoTags, err := listLocalDockerImages(toComplete)
if err != nil {
// Indicates that an error occurred and completions should be ignored
return []string{"completion failed"}, cobra.ShellCompDirectiveError
}
if len(dockerImageRepoTags) == 0 {
return []string{"no docker images found"}, cobra.ShellCompDirectiveError
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "fish", "zsh"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
switch args[0] {
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
}
return err
},
}
// ShellCompDirectiveDefault indicates that the shell will perform its default behavior after completions have
// been provided (without implying other possible directives)
return dockerImageRepoTags, cobra.ShellCompDirectiveDefault
}

func listLocalDockerImages(prefix string) ([]string, error) {
Expand Down Expand Up @@ -108,3 +91,18 @@ func listLocalDockerImages(prefix string) ([]string, error) {
}
return repoTags, nil
}

func dockerImageValidArgsFunction(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Since we use ValidArgsFunction, Cobra will call this AFTER having parsed all flags and arguments provided
dockerImageRepoTags, err := listLocalDockerImages(toComplete)
if err != nil {
// Indicates that an error occurred and completions should be ignored
return []string{"completion failed"}, cobra.ShellCompDirectiveError
}
if len(dockerImageRepoTags) == 0 {
return []string{"no docker images found"}, cobra.ShellCompDirectiveError
}
// ShellCompDirectiveDefault indicates that the shell will perform its default behavior after completions have
// been provided (without implying other possible directives)
return dockerImageRepoTags, cobra.ShellCompDirectiveDefault
}
37 changes: 37 additions & 0 deletions cmd/grype/cli/commands/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package commands

import (
"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/options"
)

type DBOptions struct {
DB options.Database `yaml:"db" json:"db" mapstructure:"db"`
}

func dbOptionsDefault(id clio.Identification) *DBOptions {
return &DBOptions{
DB: options.DefaultDatabase(id),
}
}

func DB(app clio.Application) *cobra.Command {
db := &cobra.Command{
Use: "db",
Short: "vulnerability database operations",
}

db.AddCommand(
DBCheck(app),
DBDelete(app),
DBDiff(app),
DBImport(app),
DBList(app),
DBStatus(app),
DBUpdate(app),
)

return db
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
package legacy
package commands

import (
"fmt"

"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/options"
"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/internal/bus"
)

var dbCheckCmd = &cobra.Command{
Use: "check",
Short: "check to see if there is a database update available",
Args: cobra.ExactArgs(0),
RunE: runDBCheckCmd,
func DBCheck(app clio.Application) *cobra.Command {
opts := dbOptionsDefault(app.ID())

return app.SetupCommand(&cobra.Command{
Use: "check",
Short: "check to see if there is a database update available",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return runDBCheck(opts.DB)
},
}, opts)
}

func init() {
dbCmd.AddCommand(dbCheckCmd)
}
func runDBCheck(opts options.Database) error {
defer bus.Exit()

func runDBCheckCmd(_ *cobra.Command, _ []string) error {
dbCurator, err := db.NewCurator(appConfig.DB.ToCuratorConfig())
dbCurator, err := db.NewCurator(opts.ToCuratorConfig())
if err != nil {
return err
}
Expand Down
40 changes: 40 additions & 0 deletions cmd/grype/cli/commands/db_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commands

import (
"fmt"

"github.com/spf13/cobra"

"github.com/anchore/clio"
"github.com/anchore/grype/cmd/grype/cli/options"
"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/internal/bus"
)

func DBDelete(app clio.Application) *cobra.Command {
opts := dbOptionsDefault(app.ID())

return app.SetupCommand(&cobra.Command{
Use: "delete",
Short: "delete the vulnerability database",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return runDBDelete(opts.DB)
},
}, opts)
}

func runDBDelete(opts options.Database) error {
defer bus.Exit()

dbCurator, err := db.NewCurator(opts.ToCuratorConfig())
if err != nil {
return err
}

if err := dbCurator.Delete(); err != nil {
return fmt.Errorf("unable to delete vulnerability database: %+v", err)
}

return stderrPrintLnf("Vulnerability database deleted")
}
Loading

0 comments on commit 02d513e

Please sign in to comment.