From 41db07530af5e6358b2780ac11d968ed8efabbec Mon Sep 17 00:00:00 2001 From: Brian Kassouf Date: Thu, 24 Aug 2017 15:23:40 -0700 Subject: [PATCH] Add basic autocompletion (#3223) * Add basic autocompletion * Add autocomplete to some common commands * Autocomplete the generate-root flags * Add information about autocomplete to the docs --- cli/main.go | 8 ++-- command/audit_enable.go | 17 ++++++++ command/auth.go | 48 ++++++++++++++++++++-- command/auth_enable.go | 27 ++++++++++++ command/format.go | 3 ++ command/generate-root.go | 18 ++++++++ command/init.go | 20 +++++++++ command/mount.go | 29 +++++++++++++ command/read.go | 12 ++++++ command/rekey.go | 21 ++++++++++ command/server.go | 15 +++++++ command/write.go | 13 ++++++ website/source/docs/commands/index.html.md | 16 ++++++++ 13 files changed, 240 insertions(+), 7 deletions(-) diff --git a/cli/main.go b/cli/main.go index 3d0ced3279cf..000e1e9a4e63 100644 --- a/cli/main.go +++ b/cli/main.go @@ -36,9 +36,11 @@ func RunCustom(args []string, commands map[string]cli.CommandFactory) int { } cli := &cli.CLI{ - Args: args, - Commands: commands, - HelpFunc: cli.FilteredHelpFunc(commandsInclude, HelpFunc), + Args: args, + Commands: commands, + Name: "vault", + Autocomplete: true, + HelpFunc: cli.FilteredHelpFunc(commandsInclude, HelpFunc), } exitCode, err := cli.Run() diff --git a/command/audit_enable.go b/command/audit_enable.go index 8c14b9938282..680a94ed1990 100644 --- a/command/audit_enable.go +++ b/command/audit_enable.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/vault/helper/kv-builder" "github.com/hashicorp/vault/meta" "github.com/mitchellh/mapstructure" + "github.com/posener/complete" ) // AuditEnableCommand is a Command that mounts a new mount. @@ -127,3 +128,19 @@ Audit Enable Options: ` return strings.TrimSpace(helpText) } + +func (c *AuditEnableCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictSet( + "file", + "syslog", + "socket", + ) +} + +func (c *AuditEnableCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-description": complete.PredictNothing, + "-path": complete.PredictNothing, + "-local": complete.PredictNothing, + } +} diff --git a/command/auth.go b/command/auth.go index 1d9af5feb9b6..42754aad3d94 100644 --- a/command/auth.go +++ b/command/auth.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/vault/helper/password" "github.com/hashicorp/vault/meta" "github.com/mitchellh/mapstructure" + "github.com/posener/complete" "github.com/ryanuber/columnize" ) @@ -301,15 +302,22 @@ func (c *AuthCommand) Run(args []string) int { } -func (c *AuthCommand) listMethods() int { +func (c *AuthCommand) getMethods() (map[string]*api.AuthMount, error) { client, err := c.Client() if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error initializing client: %s", err)) - return 1 + return nil, err } auth, err := client.Sys().ListAuth() + if err != nil { + return nil, err + } + + return auth, nil +} + +func (c *AuthCommand) listMethods() int { + auth, err := c.getMethods() if err != nil { c.Ui.Error(fmt.Sprintf( "Error reading auth table: %s", err)) @@ -458,3 +466,35 @@ tokens are created via the API or command line interface (with the return strings.TrimSpace(help) } + +func (c *AuthCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *AuthCommand) AutocompleteFlags() complete.Flags { + var predictFunc complete.PredictFunc = func(a complete.Args) []string { + auths, err := c.getMethods() + if err != nil { + return []string{} + } + + methods := make([]string, 0, len(auths)) + for _, auth := range auths { + if strings.HasPrefix(auth.Type, a.Last) { + methods = append(methods, auth.Type) + } + } + + return methods + } + + return complete.Flags{ + "-method": predictFunc, + "-methods": complete.PredictNothing, + "-method-help": complete.PredictNothing, + "-no-verify": complete.PredictNothing, + "-no-store": complete.PredictNothing, + "-token-only": complete.PredictNothing, + "-path": complete.PredictNothing, + } +} diff --git a/command/auth_enable.go b/command/auth_enable.go index 2c72a04640c7..3b18745c889e 100644 --- a/command/auth_enable.go +++ b/command/auth_enable.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // AuthEnableCommand is a Command that enables a new endpoint. @@ -110,3 +111,29 @@ Auth Enable Options: ` return strings.TrimSpace(helpText) } + +func (c *AuthEnableCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictSet( + "approle", + "cert", + "aws", + "app-id", + "gcp", + "github", + "userpass", + "ldap", + "okta", + "radius", + "plugin", + ) + +} + +func (c *AuthEnableCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-description": complete.PredictNothing, + "-path": complete.PredictNothing, + "-plugin-name": complete.PredictNothing, + "-local": complete.PredictNothing, + } +} diff --git a/command/format.go b/command/format.go index 286b9670e4b0..38f24d49e799 100644 --- a/command/format.go +++ b/command/format.go @@ -14,9 +14,12 @@ import ( "github.com/ghodss/yaml" "github.com/hashicorp/vault/api" "github.com/mitchellh/cli" + "github.com/posener/complete" "github.com/ryanuber/columnize" ) +var predictFormat complete.Predictor = complete.PredictSet("json", "yaml") + func OutputSecret(ui cli.Ui, format string, secret *api.Secret) int { return outputWithFormat(ui, format, secret, secret) } diff --git a/command/generate-root.go b/command/generate-root.go index f01329426de3..2d9521b7df58 100644 --- a/command/generate-root.go +++ b/command/generate-root.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/helper/xor" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // GenerateRootCommand is a Command that generates a new root token. @@ -352,3 +353,20 @@ Generate Root Options: ` return strings.TrimSpace(helpText) } + +func (c *GenerateRootCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *GenerateRootCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-init": complete.PredictNothing, + "-cancel": complete.PredictNothing, + "-status": complete.PredictNothing, + "-decode": complete.PredictNothing, + "-genotp": complete.PredictNothing, + "-otp": complete.PredictNothing, + "-pgp-key": complete.PredictNothing, + "-nonce": complete.PredictNothing, + } +} diff --git a/command/init.go b/command/init.go index 42002043f406..470c325107cc 100644 --- a/command/init.go +++ b/command/init.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/meta" "github.com/hashicorp/vault/physical/consul" + "github.com/posener/complete" ) // InitCommand is a Command that initializes a new Vault server. @@ -384,3 +385,22 @@ Init Options: ` return strings.TrimSpace(helpText) } + +func (c *InitCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *InitCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-check": complete.PredictNothing, + "-key-shares": complete.PredictNothing, + "-key-threshold": complete.PredictNothing, + "-pgp-keys": complete.PredictNothing, + "-root-token-pgp-key": complete.PredictNothing, + "-recovery-shares": complete.PredictNothing, + "-recovery-threshold": complete.PredictNothing, + "-recovery-pgp-keys": complete.PredictNothing, + "-auto": complete.PredictNothing, + "-consul-service": complete.PredictNothing, + } +} diff --git a/command/mount.go b/command/mount.go index c918a864e4a3..895e7b8bc0f7 100644 --- a/command/mount.go +++ b/command/mount.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // MountCommand is a Command that mounts a new mount. @@ -133,3 +134,31 @@ Mount Options: ` return strings.TrimSpace(helpText) } + +func (c *MountCommand) AutocompleteArgs() complete.Predictor { + // This list does not contain deprecated backends + return complete.PredictSet( + "aws", + "consul", + "pki", + "transit", + "ssh", + "rabbitmq", + "database", + "totp", + "plugin", + ) + +} + +func (c *MountCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-description": complete.PredictNothing, + "-path": complete.PredictNothing, + "-default-lease-ttl": complete.PredictNothing, + "-max-lease-ttl": complete.PredictNothing, + "-force-no-cache": complete.PredictNothing, + "-plugin-name": complete.PredictNothing, + "-local": complete.PredictNothing, + } +} diff --git a/command/read.go b/command/read.go index 6e9c4d7ebfd7..d98917822961 100644 --- a/command/read.go +++ b/command/read.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // ReadCommand is a Command that reads data from the Vault. @@ -95,3 +96,14 @@ Read Options: ` return strings.TrimSpace(helpText) } + +func (c *ReadCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *ReadCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-format": predictFormat, + "-field": complete.PredictNothing, + } +} diff --git a/command/rekey.go b/command/rekey.go index 16022be72589..bf47c2c0e9a2 100644 --- a/command/rekey.go +++ b/command/rekey.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/vault/helper/password" "github.com/hashicorp/vault/helper/pgpkeys" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // RekeyCommand is a Command that rekeys the vault. @@ -418,3 +419,23 @@ Rekey Options: ` return strings.TrimSpace(helpText) } + +func (c *RekeyCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *RekeyCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-init": complete.PredictNothing, + "-cancel": complete.PredictNothing, + "-status": complete.PredictNothing, + "-retrieve": complete.PredictNothing, + "-delete": complete.PredictNothing, + "-key-shares": complete.PredictNothing, + "-key-threshold": complete.PredictNothing, + "-nonce": complete.PredictNothing, + "-pgp-keys": complete.PredictNothing, + "-backup": complete.PredictNothing, + "-recovery-key": complete.PredictNothing, + } +} diff --git a/command/server.go b/command/server.go index 09fd651af74b..0b5f8081009c 100644 --- a/command/server.go +++ b/command/server.go @@ -23,6 +23,7 @@ import ( colorable "github.com/mattn/go-colorable" log "github.com/mgutz/logxi/v1" testing "github.com/mitchellh/go-testing-interface" + "github.com/posener/complete" "google.golang.org/grpc/grpclog" @@ -1200,6 +1201,20 @@ General Options: return strings.TrimSpace(helpText) } +func (c *ServerCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *ServerCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-config": complete.PredictOr(complete.PredictFiles("*.hcl"), complete.PredictFiles("*.json")), + "-dev": complete.PredictNothing, + "-dev-root-token-id": complete.PredictNothing, + "-dev-listen-address": complete.PredictNothing, + "-log-level": complete.PredictSet("trace", "debug", "info", "warn", "err"), + } +} + // MakeShutdownCh returns a channel that can be used for shutdown // notifications for commands. This channel will send a message for every // SIGINT or SIGTERM received. diff --git a/command/write.go b/command/write.go index 21478f9f00f5..6f7b495b402e 100644 --- a/command/write.go +++ b/command/write.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/vault/helper/kv-builder" "github.com/hashicorp/vault/meta" + "github.com/posener/complete" ) // WriteCommand is a Command that puts data into the Vault. @@ -139,3 +140,15 @@ Write Options: ` return strings.TrimSpace(helpText) } + +func (c *WriteCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *WriteCommand) AutocompleteFlags() complete.Flags { + return complete.Flags{ + "-force": complete.PredictNothing, + "-format": predictFormat, + "-field": complete.PredictNothing, + } +} diff --git a/website/source/docs/commands/index.html.md b/website/source/docs/commands/index.html.md index 08e106965894..297f1e850dd5 100644 --- a/website/source/docs/commands/index.html.md +++ b/website/source/docs/commands/index.html.md @@ -24,3 +24,19 @@ with the `-h` argument. The help output is very comprehensive, so we defer you to that for documentation. We've included some guides to the left of common interactions with the CLI. + +## Autocompletion + +The `vault` command features opt-in subcommand autocompletion that you can +enable for your shell with `vault -autocomplete-install`. After doing so, +you can invoke a new shell and use the feature. + +For example, assume a tab is typed at the end of each prompt line: + +``` +$ vault au +audit-disable audit-enable audit-list auth auth-disable auth-enable + +$ vault s +seal server ssh status step-down +```