Skip to content

Commit

Permalink
Merge pull request #133 from cloudskiff/completion
Browse files Browse the repository at this point in the history
Driftctl completion command
  • Loading branch information
eliecharra authored Jan 22, 2021
2 parents 6ec1fc9 + 639b9b2 commit c9192c3
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 1 deletion.
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ Details of reported data can be found [here](./cmd/flags/error-reporting.md)
- [Filtering resources](cmd/scan/filter.md)
- [Supported remotes](cmd/scan/supported_resources/README.md)
- [Iac sources](cmd/scan/iac_source.md)
- [Completion](cmd/completion/script.md)

63 changes: 63 additions & 0 deletions doc/cmd/completion/script.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Driftctl completion script

Driftctl can output completion script (also known as *tab completion*) for you to use on your shell. Currently `bash`, `zsh`, `fish` and `powershell` shells are supported.

### Before you start
In order to generate the completion script required to make the completion work, you have to install driftctl CLI first.

### Generate the completion file
To generate the completion script you can use:

```shell
$ driftctl completion [bash|zsh|fish|powershell]
```

By default, this command will print on the standard output the content of the completion script. To make the completion work you will need to redirect it to the completion folder of your shell.

### Bash
```shell
# Linux:
$ driftctl completion bash > /etc/bash_completion.d/driftctl

# MacOS:
$ driftctl completion bash > /usr/local/etc/bash_completion.d/driftctl
```

Remember to open a new shell to test the functionality.

### Zsh
If shell completion is not already enabled in your environment, you will need to enable it. You can execute the following once:

```shell
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
```

At this point you can generate and place the completion script in your completion folder listed in your `fpath` if it already exists. Otherwise, you can create a directory, add it to your `fpath` and copy the file in it:

```shell
$ driftctl completion zsh > fpath/completion_folder/_driftctl
```

#### Oh-My-Zsh
```shell
$ mkdir -p ~/.oh-my-zsh/completions
$ driftctl completion zsh > ~/.oh-my-zsh/completions/_driftctl
```

You will need to start a new shell for this setup to take effect.

### Fish
```shell
$ driftctl completion fish > ~/.config/fish/completions/driftctl.fish
```

Remember to create the directory if it's not already there `mkdir -p ~/.config/fish/completions/`.

Remember to open a new shell to test the functionality.

### Powershell
```shell
$ driftctl completion powershell > driftctl.ps1
```

You will need to source this file from your powershell profile for this to work as expected.
31 changes: 31 additions & 0 deletions pkg/cmd/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmd

import (
"github.com/spf13/cobra"
)

func NewCompletionCmd() *cobra.Command {
var cmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: "Generate completion script for various shells",
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(cmd.OutOrStdout())
case "zsh":
return cmd.Root().GenZshCompletion(cmd.OutOrStdout())
case "fish":
return cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true)
case "powershell":
return cmd.Root().GenPowerShellCompletion(cmd.OutOrStdout())
default:
return nil
}
},
}
return cmd
}
72 changes: 72 additions & 0 deletions pkg/cmd/completion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cmd

import (
"fmt"
"strings"
"testing"

"github.com/cloudskiff/driftctl/test"

"github.com/spf13/cobra"
)

func TestCompletionCmd(t *testing.T) {
rootCmd := &cobra.Command{Use: "root"}
rootCmd.AddCommand(NewCompletionCmd())

tests := []struct {
name string
args []string
expected string
err error
}{
{
name: "Without args",
args: []string{"completion"},
err: fmt.Errorf("accepts 1 arg(s), received 0"),
},
{
name: "With wrong arg",
args: []string{"completion", "test"},
err: fmt.Errorf("invalid argument \"test\" for \"root completion\""),
},
{
name: "With multiple args",
args: []string{"completion", "bash", "zsh"},
err: fmt.Errorf("accepts 1 arg(s), received 2"),
},
{
name: "With bash arg",
args: []string{"completion", "bash"},
expected: "# bash completion for root",
},
{
name: "With zsh arg",
args: []string{"completion", "zsh"},
expected: "#compdef _root root",
},
{
name: "With fish arg",
args: []string{"completion", "fish"},
expected: "# fish completion for root",
},
{
name: "With powershell arg",
args: []string{"completion", "powershell"},
expected: "Register-ArgumentCompleter -Native -CommandName 'root'",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := test.Execute(rootCmd, tt.args...)

if tt.expected != "" && !strings.Contains(output, tt.expected) {
t.Errorf("Expected to contain: \n %v\nGot:\n %v", tt.expected, output)
}
if tt.err != nil && tt.err.Error() != err.Error() {
t.Errorf("Expected %v, got %v", tt.err, err)
}
})
}
}
5 changes: 4 additions & 1 deletion pkg/cmd/driftctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func NewDriftctlCmd(build build.BuildInterface) *DriftctlCmd {
cmd.SetVersionTemplate(versionTemplate)
cmd.AddCommand(NewVersionCmd())

cmd.AddCommand(NewCompletionCmd())

cmd.SetUsageTemplate(usageTemplate)

cmd.PersistentFlags().BoolP("help", "h", false, "Display help for command")
Expand All @@ -81,8 +83,9 @@ func (driftctlCmd DriftctlCmd) ShouldCheckVersion() bool {
noVersionCheckEnv := os.Getenv("DCTL_NO_VERSION_CHECK") == "true"
noVersionCheckVal := contains(os.Args[1:], "--no-version-check")
hasVersionCmd := contains(os.Args[1:], "version")
hasCompletionCmd := contains(os.Args[1:], "completion")
isHelp := contains(os.Args[1:], "help") || contains(os.Args[1:], "--help") || contains(os.Args[1:], "-h")
return driftctlCmd.build.IsRelease() && !hasVersionCmd && !noVersionCheckVal && !isHelp && !noVersionCheckEnv
return driftctlCmd.build.IsRelease() && !hasVersionCmd && !hasCompletionCmd && !noVersionCheckVal && !isHelp && !noVersionCheckEnv
}

func IsReportingEnabled(cmd *cobra.Command) bool {
Expand Down
23 changes: 23 additions & 0 deletions pkg/cmd/driftctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ func TestDriftctlCmd_Version(t *testing.T) {
}
}

func TestDriftctlCmd_Completion(t *testing.T) {
cmd := NewDriftctlCmd(mocks.MockBuild{})

output, err := test.Execute(&cmd.Command, "completion", "bash")
if output == "" {
t.Errorf("Unexpected output: %v", output)
}
if err != nil {
t.Errorf("Unexpected error: %v", err)
}

expected := "# bash completion for driftctl"
if !strings.Contains(output, expected) {
t.Errorf("Expected to contain: \n %v\nGot:\n %v", expected, output)
}
}

func TestDriftctlCmd_Scan(t *testing.T) {

cases := []struct {
Expand Down Expand Up @@ -227,6 +244,12 @@ func TestDriftctlCmd_ShouldCheckVersion(t *testing.T) {
args: []string{"scan", "--from", "tfstate://terraform.tfstate"},
expected: false,
},
{
Name: "Don't check for update for completion cmd",
IsRelease: true,
args: []string{"completion", "bash"},
expected: false,
},
}

for _, c := range cases {
Expand Down

0 comments on commit c9192c3

Please sign in to comment.