diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 000000000..7ec259b58 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish]", + Short: "Generate a shell completion for Syft (listing local docker images)", + Long: `To load completions (docker image list): + +Bash: + +$ source <(syft completion bash) + +# To load completions for each session, execute once: +Linux: + $ syft completion bash > /etc/bash_completion.d/syft +MacOS: + $ syft completion bash > /usr/local/etc/bash_completion.d/syft + +Zsh: + +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +# To load completions for each session, execute once: +$ syft completion zsh > "${fpath[1]}/_syft" + +# You will need to start a new shell for this setup to take effect. + +Fish: + +$ syft completion fish | source + +# To load completions for each session, execute once: +$ syft completion fish > ~/.config/fish/completions/syft.fish +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var err error + switch args[0] { + case "bash": + err = cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + err = cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + err = cmd.Root().GenFishCompletion(os.Stdout, true) + } + if err != nil { + panic(err) + } + }, +} + +func init() { + rootCmd.AddCommand(completionCmd) +} diff --git a/cmd/root.go b/cmd/root.go index af02fd981..9e37e11aa 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,8 +1,14 @@ package cmd import ( + "context" "fmt" "os" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/bus" @@ -44,6 +50,20 @@ Supports the following image sources: os.Exit(1) } }, + ValidArgsFunction: func(cmd *cobra.Command, args []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 + }, } func startWorker(userInput string) <-chan error { @@ -88,3 +108,30 @@ func doRunCmd(_ *cobra.Command, args []string) error { ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet) return ux(errs, eventSubscription) } + +func ListLocalDockerImages(prefix string) ([]string, error) { + var repoTags = make([]string, 0) + ctx := context.Background() + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return repoTags, err + } + + // Only want to return tagged images + imageListArgs := filters.NewArgs() + imageListArgs.Add("dangling", "false") + images, err := cli.ImageList(ctx, types.ImageListOptions{All: false, Filters: imageListArgs}) + if err != nil { + return repoTags, err + } + + for _, image := range images { + // image may have multiple tags + for _, tag := range image.RepoTags { + if strings.HasPrefix(tag, prefix) { + repoTags = append(repoTags, tag) + } + } + } + return repoTags, nil +} diff --git a/go.mod b/go.mod index 754f964db..5fefcbf7e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/anchore/stereoscope v0.0.0-20200813152757-548b22c8a0b3 github.com/bmatcuk/doublestar v1.3.1 github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect + github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible github.com/dustin/go-humanize v1.0.0 github.com/go-test/deep v1.0.6 github.com/google/go-containerregistry v0.1.1 // indirect @@ -26,7 +27,7 @@ require ( github.com/rogpeppe/go-internal v1.5.2 github.com/sergi/go-diff v1.1.0 github.com/sirupsen/logrus v1.6.0 - github.com/spf13/cobra v1.0.0 + github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905 github.com/spf13/viper v1.7.0 github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240 diff --git a/go.sum b/go.sum index 9c65daa48..796d69567 100644 --- a/go.sum +++ b/go.sum @@ -766,6 +766,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905 h1:Xn3y6St5vlEgvxekK6JuArarc79GKAj/jbcsMRCaRH4= +github.com/spf13/cobra v1.0.1-0.20200909172742-8a63648dd905/go.mod h1:jCpPr/pv3OLY1D1nS9lN5HIdIpmOO/WTYJcAmnPq1nU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= diff --git a/resources/shell-completion.sh b/resources/shell-completion.sh deleted file mode 100644 index 02e87775c..000000000 --- a/resources/shell-completion.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -# Source this file in your shell sessions in order to use this shell completion. - -complete -W "$(docker images --format '{{.Repository}}:{{.Tag}}')" syft