From 579794db2a0faf154daadff81ab2a5818188012c Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 13:23:27 -0400 Subject: [PATCH 01/12] Add 'plugin list' command --- command/commands.go | 5 ++ command/plugin_list.go | 89 +++++++++++++++++++++++++++++++++ command/plugin_list_test.go | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 command/plugin_list.go create mode 100644 command/plugin_list_test.go diff --git a/command/commands.go b/command/commands.go index b67b31da379c..e916f8dc9548 100644 --- a/command/commands.go +++ b/command/commands.go @@ -361,6 +361,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin list": func() (cli.Command, error) { + return &PluginListCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "policy": func() (cli.Command, error) { return &PolicyCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin_list.go b/command/plugin_list.go new file mode 100644 index 000000000000..4e8375c66266 --- /dev/null +++ b/command/plugin_list.go @@ -0,0 +1,89 @@ +package command + +import ( + "fmt" + "sort" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*PluginListCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginListCommand)(nil) + +type PluginListCommand struct { + *BaseCommand +} + +func (c *PluginListCommand) Synopsis() string { + return "Lists available plugins" +} + +func (c *PluginListCommand) Help() string { + helpText := ` +Usage: vault plugin list [options] + + Lists available plugins registered in the catalog. This does not list whether + plugins are in use, but rather just their availability. + + List all available plugins in the catalog: + + $ vault plugin list + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PluginListCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP | FlagSetOutputFormat) +} + +func (c *PluginListCommand) AutocompleteArgs() complete.Predictor { + return complete.PredictNothing +} + +func (c *PluginListCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PluginListCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + if len(args) > 0 { + c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args))) + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + resp, err := client.Sys().ListPlugins(&api.ListPluginsInput{}) + if err != nil { + c.UI.Error(fmt.Sprintf("Error listing available plugins: %s", err)) + return 2 + } + + pluginNames := resp.Names + sort.Strings(pluginNames) + + switch Format(c.UI) { + case "table": + list := append([]string{"Plugins"}, pluginNames...) + c.UI.Output(tableOutput(list, nil)) + return 0 + default: + return OutputData(c.UI, pluginNames) + } +} diff --git a/command/plugin_list_test.go b/command/plugin_list_test.go new file mode 100644 index 000000000000..86a274d71fd3 --- /dev/null +++ b/command/plugin_list_test.go @@ -0,0 +1,99 @@ +package command + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func testPluginListCommand(tb testing.TB) (*cli.MockUi, *PluginListCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &PluginListCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestPluginListCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "too_many_args", + []string{"foo"}, + "Too many arguments", + 1, + }, + { + "lists", + nil, + "Plugins", + 0, + }, + } + + t.Run("validations", func(t *testing.T) { + t.Parallel() + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testPluginListCommand(t) + cmd.client = client + + code := cmd.Run(tc.args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testPluginListCommand(t) + cmd.client = client + + code := cmd.Run([]string{}) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error listing available plugins: " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testPluginListCommand(t) + assertNoTabs(t, cmd) + }) +} From b6454f81050608f1f735f769406d06d8cc67f102 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 14:18:19 -0400 Subject: [PATCH 02/12] Add 'plugin register' command --- command/command_test.go | 17 ++++ command/commands.go | 5 + command/plugin_register.go | 135 ++++++++++++++++++++++++++ command/plugin_register_test.go | 167 ++++++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 command/plugin_register.go create mode 100644 command/plugin_register_test.go diff --git a/command/command_test.go b/command/command_test.go index 77598a9a424c..ecfc7441d880 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -93,6 +93,23 @@ func testVaultServerUnseal(tb testing.TB) (*api.Client, []string, func()) { }) } +// testVaultServerUnseal creates a test vault cluster and returns a configured +// API client, list of unseal keys (as strings), and a closer function +// configured with the given plugin directory. +func testVaultServerPluginDir(tb testing.TB, pluginDir string) (*api.Client, []string, func()) { + tb.Helper() + + return testVaultServerCoreConfig(tb, &vault.CoreConfig{ + DisableMlock: true, + DisableCache: true, + Logger: defaultVaultLogger, + CredentialBackends: defaultVaultCredentialBackends, + AuditBackends: defaultVaultAuditBackends, + LogicalBackends: defaultVaultLogicalBackends, + PluginDirectory: pluginDir, + }) +} + // testVaultServerCoreConfig creates a new vault cluster with the given core // configuration. This is a lower-level test helper. func testVaultServerCoreConfig(tb testing.TB, coreConfig *vault.CoreConfig) (*api.Client, []string, func()) { diff --git a/command/commands.go b/command/commands.go index e916f8dc9548..91fcd7c73b6a 100644 --- a/command/commands.go +++ b/command/commands.go @@ -366,6 +366,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin register": func() (cli.Command, error) { + return &PluginRegisterCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "policy": func() (cli.Command, error) { return &PolicyCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin_register.go b/command/plugin_register.go new file mode 100644 index 000000000000..3558cb576f5e --- /dev/null +++ b/command/plugin_register.go @@ -0,0 +1,135 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*PluginRegisterCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginRegisterCommand)(nil) + +type PluginRegisterCommand struct { + *BaseCommand + + flagArgs []string + flagCommand string + flagSHA256 string +} + +func (c *PluginRegisterCommand) Synopsis() string { + return "Registers a new plugin in the catalog" +} + +func (c *PluginRegisterCommand) Help() string { + helpText := ` +Usage: vault plugin register [options] NAME + + Registers a new plugin in the catalog. The plugin binary must exist in Vault's + configured plugin directory. + + Register the plugin named my-custom-plugin: + + $ vault plugin register my-custom-plugin \ + -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 + + Register a plugin with custom arguments: + + $ vault plugin register my-custom-plugin \ + -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 \ + -args=--with-glibc,--with-cgo + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PluginRegisterCommand) Flags() *FlagSets { + set := c.flagSet(FlagSetHTTP) + + f := set.NewFlagSet("Command Options") + + f.StringSliceVar(&StringSliceVar{ + Name: "args", + Target: &c.flagArgs, + Completion: complete.PredictAnything, + Usage: "Arguments to pass to the plugin when starting. Separate " + + "multiple arguments with a comma.", + }) + + f.StringVar(&StringVar{ + Name: "command", + Target: &c.flagCommand, + Completion: complete.PredictAnything, + Usage: "Command to spawn the plugin. This defaults to the name of the " + + "plugin if unspecified.", + }) + + f.StringVar(&StringVar{ + Name: "sha256", + Target: &c.flagSHA256, + Completion: complete.PredictAnything, + Usage: "SHA256 of the plugin binary. This is required for all plugins.", + }) + + return set +} + +func (c *PluginRegisterCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultPlugins() +} + +func (c *PluginRegisterCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PluginRegisterCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + switch { + case len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) + return 1 + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) + return 1 + case c.flagSHA256 == "": + c.UI.Error("SHA256 is required for all plugins, please provide -sha256") + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + pluginName := strings.TrimSpace(args[0]) + + command := c.flagCommand + if command == "" { + command = pluginName + } + + if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{ + Name: pluginName, + Args: c.flagArgs, + Command: command, + SHA256: c.flagSHA256, + }); err != nil { + c.UI.Error(fmt.Sprintf("Error registering plugin %s: %s", pluginName, err)) + return 2 + } + + c.UI.Output(fmt.Sprintf("Success! Registered plugin %s", pluginName)) + return 0 +} diff --git a/command/plugin_register_test.go b/command/plugin_register_test.go new file mode 100644 index 000000000000..ddea812e2f8e --- /dev/null +++ b/command/plugin_register_test.go @@ -0,0 +1,167 @@ +package command + +import ( + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" +) + +func testPluginRegisterCommand(tb testing.TB) (*cli.MockUi, *PluginRegisterCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &PluginRegisterCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestPluginRegisterCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "not_enough_args", + nil, + "Not enough arguments", + 1, + }, + { + "too_many_args", + []string{"foo", "bar"}, + "Too many arguments", + 1, + }, + { + "not_a_plugin", + []string{"nope_definitely_never_a_plugin_nope"}, + "", + 2, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testPluginRegisterCommand(t) + cmd.client = client + + args := append([]string{"-sha256", "abcd1234"}, tc.args...) + code := cmd.Run(args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } + + t.Run("integration", func(t *testing.T) { + t.Parallel() + + pluginName := "my-plugin" + + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + // OSX tempdir are /var, but actually symlinked to /private/var + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + log.Fatal(err) + } + + if err := ioutil.WriteFile(dir+"/"+pluginName, nil, 0755); err != nil { + t.Fatal(err) + } + + client, _, closer := testVaultServerPluginDir(t, dir) + defer closer() + + ui, cmd := testPluginRegisterCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "-sha256", "abcd1234", + pluginName, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Success! Registered plugin my-plugin" + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + + resp, err := client.Sys().ListPlugins(&api.ListPluginsInput{}) + if err != nil { + t.Fatal(err) + } + + found := false + for _, p := range resp.Names { + if p == pluginName { + found = true + } + } + if !found { + t.Errorf("expected %q to be in %q", pluginName, resp.Names) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testPluginRegisterCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "-sha256", "abcd1234", + "my-plugin", + }) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error registering plugin my-plugin:" + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testPluginRegisterCommand(t) + assertNoTabs(t, cmd) + }) +} From 3bce378ffae6f285dfee8f79aa79cd50ab8e1dc6 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 14:44:35 -0400 Subject: [PATCH 03/12] Add 'plugin deregister' command --- command/commands.go | 5 + command/plugin_deregister.go | 86 ++++++++++++++ command/plugin_deregister_test.go | 187 ++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 command/plugin_deregister.go create mode 100644 command/plugin_deregister_test.go diff --git a/command/commands.go b/command/commands.go index 91fcd7c73b6a..521ab1c7ea19 100644 --- a/command/commands.go +++ b/command/commands.go @@ -361,6 +361,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin deregister": func() (cli.Command, error) { + return &PluginDeregisterCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "plugin list": func() (cli.Command, error) { return &PluginListCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin_deregister.go b/command/plugin_deregister.go new file mode 100644 index 000000000000..e9669cae5109 --- /dev/null +++ b/command/plugin_deregister.go @@ -0,0 +1,86 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*PluginDeregisterCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginDeregisterCommand)(nil) + +type PluginDeregisterCommand struct { + *BaseCommand +} + +func (c *PluginDeregisterCommand) Synopsis() string { + return "Deregister an existing plugin in the catalog" +} + +func (c *PluginDeregisterCommand) Help() string { + helpText := ` +Usage: vault plugin deregister [options] NAME + + Deregister an existing plugin in the catalog. This command requires sudo + privledges. + + Deregister the plugin named my-custom-plugin: + + $ vault plugin deregister my-custom-plugin + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PluginDeregisterCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP) +} + +func (c *PluginDeregisterCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultPlugins() +} + +func (c *PluginDeregisterCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PluginDeregisterCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + switch { + case len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) + return 1 + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + pluginName := strings.TrimSpace(args[0]) + + if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{ + Name: pluginName, + }); err != nil { + c.UI.Error(fmt.Sprintf("Error deregistering plugin %s: %s", pluginName, err)) + return 2 + } + + c.UI.Output(fmt.Sprintf("Success! Deregistered plugin (if it was registered): %s", pluginName)) + return 0 +} diff --git a/command/plugin_deregister_test.go b/command/plugin_deregister_test.go new file mode 100644 index 000000000000..a30ea64190e3 --- /dev/null +++ b/command/plugin_deregister_test.go @@ -0,0 +1,187 @@ +package command + +import ( + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" +) + +func testPluginDeregisterCommand(tb testing.TB) (*cli.MockUi, *PluginDeregisterCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &PluginDeregisterCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestPluginDeregisterCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "not_enough_args", + nil, + "Not enough arguments", + 1, + }, + { + "too_many_args", + []string{"foo", "bar"}, + "Too many arguments", + 1, + }, + { + "not_a_plugin", + []string{"nope_definitely_never_a_plugin_nope"}, + "", + 0, + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testPluginDeregisterCommand(t) + cmd.client = client + + code := cmd.Run(tc.args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } + + t.Run("integration", func(t *testing.T) { + t.Parallel() + + pluginName := "my-plugin" + + dir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + // OSX tempdir are /var, but actually symlinked to /private/var + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + log.Fatal(err) + } + + pth := dir + "/" + pluginName + if err := ioutil.WriteFile(pth, nil, 0755); err != nil { + t.Fatal(err) + } + + f, err := os.Open(pth) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + t.Fatal(err) + } + + client, _, closer := testVaultServerPluginDir(t, dir) + defer closer() + + ui, cmd := testPluginDeregisterCommand(t) + cmd.client = client + + if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{ + Name: pluginName, + Command: pluginName, + SHA256: fmt.Sprintf("%x", h.Sum(nil)), + }); err != nil { + t.Fatal(err) + } + + code := cmd.Run([]string{ + pluginName, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Success! Deregistered plugin (if it was registered): " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + + resp, err := client.Sys().ListPlugins(&api.ListPluginsInput{}) + if err != nil { + t.Fatal(err) + } + + found := false + for _, p := range resp.Names { + if p == pluginName { + found = true + } + } + if found { + t.Errorf("expected %q to not be in %q", pluginName, resp.Names) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testPluginDeregisterCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "my-plugin", + }) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error deregistering plugin my-plugin: " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testPluginDeregisterCommand(t) + assertNoTabs(t, cmd) + }) +} From a7e5f973b237aef725f532cb507fcfb7819d66a8 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 19:33:35 -0400 Subject: [PATCH 04/12] Use a shared plugin helper --- command/plugin_deregister.go | 2 +- command/plugin_deregister_test.go | 47 ++++--------------- command/plugin_register_test.go | 30 +++--------- command/plugin_test.go | 78 +++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 63 deletions(-) create mode 100644 command/plugin_test.go diff --git a/command/plugin_deregister.go b/command/plugin_deregister.go index e9669cae5109..370aa40a66ae 100644 --- a/command/plugin_deregister.go +++ b/command/plugin_deregister.go @@ -77,7 +77,7 @@ func (c *PluginDeregisterCommand) Run(args []string) int { if err := client.Sys().DeregisterPlugin(&api.DeregisterPluginInput{ Name: pluginName, }); err != nil { - c.UI.Error(fmt.Sprintf("Error deregistering plugin %s: %s", pluginName, err)) + c.UI.Error(fmt.Sprintf("Error deregistering plugin named %s: %s", pluginName, err)) return 2 } diff --git a/command/plugin_deregister_test.go b/command/plugin_deregister_test.go index a30ea64190e3..4d326aafc905 100644 --- a/command/plugin_deregister_test.go +++ b/command/plugin_deregister_test.go @@ -1,13 +1,6 @@ package command import ( - "crypto/sha256" - "fmt" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" "strings" "testing" @@ -82,46 +75,22 @@ func TestPluginDeregisterCommand_Run(t *testing.T) { t.Run("integration", func(t *testing.T) { t.Parallel() - pluginName := "my-plugin" - - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // OSX tempdir are /var, but actually symlinked to /private/var - dir, err = filepath.EvalSymlinks(dir) - if err != nil { - log.Fatal(err) - } - - pth := dir + "/" + pluginName - if err := ioutil.WriteFile(pth, nil, 0755); err != nil { - t.Fatal(err) - } - - f, err := os.Open(pth) - if err != nil { - t.Fatal(err) - } - defer f.Close() + pluginDir, cleanup := testPluginDir(t) + defer cleanup(t) - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - t.Fatal(err) - } - - client, _, closer := testVaultServerPluginDir(t, dir) + client, _, closer := testVaultServerPluginDir(t, pluginDir) defer closer() + pluginName := "my-plugin" + _, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName) + ui, cmd := testPluginDeregisterCommand(t) cmd.client = client if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{ Name: pluginName, Command: pluginName, - SHA256: fmt.Sprintf("%x", h.Sum(nil)), + SHA256: sha256Sum, }); err != nil { t.Fatal(err) } @@ -171,7 +140,7 @@ func TestPluginDeregisterCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Error deregistering plugin my-plugin: " + expected := "Error deregistering plugin named my-plugin: " combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) diff --git a/command/plugin_register_test.go b/command/plugin_register_test.go index ddea812e2f8e..2bd07aaf9d2a 100644 --- a/command/plugin_register_test.go +++ b/command/plugin_register_test.go @@ -1,10 +1,6 @@ package command import ( - "io/ioutil" - "log" - "os" - "path/filepath" "strings" "testing" @@ -80,32 +76,20 @@ func TestPluginRegisterCommand_Run(t *testing.T) { t.Run("integration", func(t *testing.T) { t.Parallel() - pluginName := "my-plugin" - - dir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // OSX tempdir are /var, but actually symlinked to /private/var - dir, err = filepath.EvalSymlinks(dir) - if err != nil { - log.Fatal(err) - } - - if err := ioutil.WriteFile(dir+"/"+pluginName, nil, 0755); err != nil { - t.Fatal(err) - } + pluginDir, cleanup := testPluginDir(t) + defer cleanup(t) - client, _, closer := testVaultServerPluginDir(t, dir) + client, _, closer := testVaultServerPluginDir(t, pluginDir) defer closer() + pluginName := "my-plugin" + _, sha256Sum := testPluginCreate(t, pluginDir, pluginName) + ui, cmd := testPluginRegisterCommand(t) cmd.client = client code := cmd.Run([]string{ - "-sha256", "abcd1234", + "-sha256", sha256Sum, pluginName, }) if exp := 0; code != exp { diff --git a/command/plugin_test.go b/command/plugin_test.go new file mode 100644 index 000000000000..7f64c14721d0 --- /dev/null +++ b/command/plugin_test.go @@ -0,0 +1,78 @@ +package command + +import ( + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/hashicorp/vault/api" +) + +// testPluginDir creates a temporary directory suitable for holding plugins. +// This helper also resolves symlinks to make tests happy on OS X. +func testPluginDir(tb testing.TB) (string, func(tb testing.TB)) { + tb.Helper() + + dir, err := ioutil.TempDir("", "") + if err != nil { + tb.Fatal(err) + } + + // OSX tempdir are /var, but actually symlinked to /private/var + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + tb.Fatal(err) + } + + return dir, func(tb testing.TB) { + if err := os.RemoveAll(dir); err != nil { + tb.Fatal(err) + } + } +} + +// testPluginCreate creates a sample plugin in a tempdir and returns the shasum +// and filepath to the plugin. +func testPluginCreate(tb testing.TB, dir, name string) (string, string) { + tb.Helper() + + pth := dir + "/" + name + if err := ioutil.WriteFile(pth, nil, 0755); err != nil { + tb.Fatal(err) + } + + f, err := os.Open(pth) + if err != nil { + tb.Fatal(err) + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + tb.Fatal(err) + } + sha256Sum := fmt.Sprintf("%x", h.Sum(nil)) + + return pth, sha256Sum +} + +// testPluginCreateAndRegister creates a plugin and registers it in the catalog. +func testPluginCreateAndRegister(tb testing.TB, client *api.Client, dir, name string) (string, string) { + tb.Helper() + + pth, sha256Sum := testPluginCreate(tb, dir, name) + + if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{ + Name: name, + Command: name, + SHA256: sha256Sum, + }); err != nil { + tb.Fatal(err) + } + + return pth, sha256Sum +} From a5b031278bd2cad08b6ddd45bfc8a7546fdf28da Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 19:34:18 -0400 Subject: [PATCH 05/12] Add 'plugin read' command --- api/sys_plugins.go | 6 +- command/commands.go | 5 ++ command/plugin_read.go | 98 ++++++++++++++++++++++ command/plugin_read_test.go | 161 ++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 command/plugin_read.go create mode 100644 command/plugin_read_test.go diff --git a/api/sys_plugins.go b/api/sys_plugins.go index 8183b10f5b77..c061b45bdb35 100644 --- a/api/sys_plugins.go +++ b/api/sys_plugins.go @@ -60,12 +60,14 @@ func (c *Sys) GetPlugin(i *GetPluginInput) (*GetPluginResponse, error) { } defer resp.Body.Close() - var result GetPluginResponse + var result struct { + Data GetPluginResponse + } err = resp.DecodeJSON(&result) if err != nil { return nil, err } - return &result, err + return &result.Data, err } // RegisterPluginInput is used as input to the RegisterPlugin function. diff --git a/command/commands.go b/command/commands.go index 521ab1c7ea19..f15f6c0db917 100644 --- a/command/commands.go +++ b/command/commands.go @@ -371,6 +371,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin read": func() (cli.Command, error) { + return &PluginReadCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "plugin register": func() (cli.Command, error) { return &PluginRegisterCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin_read.go b/command/plugin_read.go new file mode 100644 index 000000000000..ec6e3e00cb61 --- /dev/null +++ b/command/plugin_read.go @@ -0,0 +1,98 @@ +package command + +import ( + "fmt" + "strings" + + "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +var _ cli.Command = (*PluginReadCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginReadCommand)(nil) + +type PluginReadCommand struct { + *BaseCommand +} + +func (c *PluginReadCommand) Synopsis() string { + return "Read information about a plugin in the catalog" +} + +func (c *PluginReadCommand) Help() string { + helpText := ` +Usage: vault plugin read [options] NAME + + Reads information about a plugin in the catalog with the given name. If the + plugin does not exist, an error is returned. This command requires sudo + privledges. + + Read a plugin: + + $ vault plugin read mysql-database-plugin + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PluginReadCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat) +} + +func (c *PluginReadCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultPlugins() +} + +func (c *PluginReadCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() +} + +func (c *PluginReadCommand) Run(args []string) int { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) + return 1 + } + + args = f.Args() + switch { + case len(args) < 1: + c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args))) + return 1 + case len(args) > 1: + c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args))) + return 1 + } + + client, err := c.Client() + if err != nil { + c.UI.Error(err.Error()) + return 2 + } + + pluginName := strings.TrimSpace(args[0]) + + resp, err := client.Sys().GetPlugin(&api.GetPluginInput{ + Name: pluginName, + }) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading plugin named %s: %s", pluginName, err)) + return 2 + } + + data := map[string]interface{}{ + "args": resp.Args, + "builtin": resp.Builtin, + "command": resp.Command, + "name": resp.Name, + "sha256": resp.SHA256, + } + + if c.flagField != "" { + return PrintRawField(c.UI, data, c.flagField) + } + return OutputData(c.UI, data) +} diff --git a/command/plugin_read_test.go b/command/plugin_read_test.go new file mode 100644 index 000000000000..3db74ca1008a --- /dev/null +++ b/command/plugin_read_test.go @@ -0,0 +1,161 @@ +package command + +import ( + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func testPluginReadCommand(tb testing.TB) (*cli.MockUi, *PluginReadCommand) { + tb.Helper() + + ui := cli.NewMockUi() + return ui, &PluginReadCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestPluginReadCommand_Run(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + args []string + out string + code int + }{ + { + "too_many_args", + []string{"foo", "bar"}, + "Too many arguments", + 1, + }, + { + "no_plugin_exist", + []string{"not-a-real-plugin-like-ever"}, + "Error reading plugin", + 2, + }, + } + + t.Run("validations", func(t *testing.T) { + t.Parallel() + + for _, tc := range cases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServer(t) + defer closer() + + ui, cmd := testPluginReadCommand(t) + cmd.client = client + + code := cmd.Run(tc.args) + if code != tc.code { + t.Errorf("expected %d to be %d", code, tc.code) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, tc.out) { + t.Errorf("expected %q to contain %q", combined, tc.out) + } + }) + } + }) + + t.Run("default", func(t *testing.T) { + t.Parallel() + + pluginDir, cleanup := testPluginDir(t) + defer cleanup(t) + + client, _, closer := testVaultServerPluginDir(t, pluginDir) + defer closer() + + pluginName := "my-plugin" + _, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName) + + ui, cmd := testPluginReadCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + pluginName, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, pluginName) { + t.Errorf("expected %q to contain %q", combined, pluginName) + } + if !strings.Contains(combined, sha256Sum) { + t.Errorf("expected %q to contain %q", combined, sha256Sum) + } + }) + + t.Run("field", func(t *testing.T) { + t.Parallel() + + pluginDir, cleanup := testPluginDir(t) + defer cleanup(t) + + client, _, closer := testVaultServerPluginDir(t, pluginDir) + defer closer() + + pluginName := "my-plugin" + testPluginCreateAndRegister(t, client, pluginDir, pluginName) + + ui, cmd := testPluginReadCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "-field", "builtin", + pluginName, + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if exp := "false"; combined != exp { + t.Errorf("expected %q to be %q", combined, exp) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testPluginReadCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "my-plugin", + }) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error reading plugin named my-plugin: " + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + }) + + t.Run("no_tabs", func(t *testing.T) { + t.Parallel() + + _, cmd := testPluginReadCommand(t) + assertNoTabs(t, cmd) + }) +} From 739577336b35a3163da145759869d3c7f66c096a Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 20:06:42 -0400 Subject: [PATCH 06/12] Rename to plugin info --- command/commands.go | 4 +-- command/{plugin_read.go => plugin_info.go} | 28 +++++++++---------- ...lugin_read_test.go => plugin_info_test.go} | 16 +++++------ 3 files changed, 24 insertions(+), 24 deletions(-) rename command/{plugin_read.go => plugin_info.go} (66%) rename command/{plugin_read_test.go => plugin_info_test.go} (89%) diff --git a/command/commands.go b/command/commands.go index f15f6c0db917..eb377174ec0b 100644 --- a/command/commands.go +++ b/command/commands.go @@ -371,8 +371,8 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, - "plugin read": func() (cli.Command, error) { - return &PluginReadCommand{ + "plugin info": func() (cli.Command, error) { + return &PluginInfoCommand{ BaseCommand: getBaseCommand(), }, nil }, diff --git a/command/plugin_read.go b/command/plugin_info.go similarity index 66% rename from command/plugin_read.go rename to command/plugin_info.go index ec6e3e00cb61..a2ca63dc1377 100644 --- a/command/plugin_read.go +++ b/command/plugin_info.go @@ -9,47 +9,47 @@ import ( "github.com/posener/complete" ) -var _ cli.Command = (*PluginReadCommand)(nil) -var _ cli.CommandAutocomplete = (*PluginReadCommand)(nil) +var _ cli.Command = (*PluginInfoCommand)(nil) +var _ cli.CommandAutocomplete = (*PluginInfoCommand)(nil) -type PluginReadCommand struct { +type PluginInfoCommand struct { *BaseCommand } -func (c *PluginReadCommand) Synopsis() string { +func (c *PluginInfoCommand) Synopsis() string { return "Read information about a plugin in the catalog" } -func (c *PluginReadCommand) Help() string { +func (c *PluginInfoCommand) Help() string { helpText := ` -Usage: vault plugin read [options] NAME +Usage: vault plugin info [options] NAME - Reads information about a plugin in the catalog with the given name. If the - plugin does not exist, an error is returned. This command requires sudo + Displays information about a plugin in the catalog with the given name. If + the plugin does not exist, an error is returned. This command requires sudo privledges. - Read a plugin: + Get info about a plugin: - $ vault plugin read mysql-database-plugin + $ vault plugin info mysql-database-plugin ` + c.Flags().Help() return strings.TrimSpace(helpText) } -func (c *PluginReadCommand) Flags() *FlagSets { +func (c *PluginInfoCommand) Flags() *FlagSets { return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat) } -func (c *PluginReadCommand) AutocompleteArgs() complete.Predictor { +func (c *PluginInfoCommand) AutocompleteArgs() complete.Predictor { return c.PredictVaultPlugins() } -func (c *PluginReadCommand) AutocompleteFlags() complete.Flags { +func (c *PluginInfoCommand) AutocompleteFlags() complete.Flags { return c.Flags().Completions() } -func (c *PluginReadCommand) Run(args []string) int { +func (c *PluginInfoCommand) Run(args []string) int { f := c.Flags() if err := f.Parse(args); err != nil { diff --git a/command/plugin_read_test.go b/command/plugin_info_test.go similarity index 89% rename from command/plugin_read_test.go rename to command/plugin_info_test.go index 3db74ca1008a..bc6e8bc3badb 100644 --- a/command/plugin_read_test.go +++ b/command/plugin_info_test.go @@ -7,18 +7,18 @@ import ( "github.com/mitchellh/cli" ) -func testPluginReadCommand(tb testing.TB) (*cli.MockUi, *PluginReadCommand) { +func testPluginInfoCommand(tb testing.TB) (*cli.MockUi, *PluginInfoCommand) { tb.Helper() ui := cli.NewMockUi() - return ui, &PluginReadCommand{ + return ui, &PluginInfoCommand{ BaseCommand: &BaseCommand{ UI: ui, }, } } -func TestPluginReadCommand_Run(t *testing.T) { +func TestPluginInfoCommand_Run(t *testing.T) { t.Parallel() cases := []struct { @@ -53,7 +53,7 @@ func TestPluginReadCommand_Run(t *testing.T) { client, closer := testVaultServer(t) defer closer() - ui, cmd := testPluginReadCommand(t) + ui, cmd := testPluginInfoCommand(t) cmd.client = client code := cmd.Run(tc.args) @@ -81,7 +81,7 @@ func TestPluginReadCommand_Run(t *testing.T) { pluginName := "my-plugin" _, sha256Sum := testPluginCreateAndRegister(t, client, pluginDir, pluginName) - ui, cmd := testPluginReadCommand(t) + ui, cmd := testPluginInfoCommand(t) cmd.client = client code := cmd.Run([]string{ @@ -112,7 +112,7 @@ func TestPluginReadCommand_Run(t *testing.T) { pluginName := "my-plugin" testPluginCreateAndRegister(t, client, pluginDir, pluginName) - ui, cmd := testPluginReadCommand(t) + ui, cmd := testPluginInfoCommand(t) cmd.client = client code := cmd.Run([]string{ @@ -135,7 +135,7 @@ func TestPluginReadCommand_Run(t *testing.T) { client, closer := testVaultServerBad(t) defer closer() - ui, cmd := testPluginReadCommand(t) + ui, cmd := testPluginInfoCommand(t) cmd.client = client code := cmd.Run([]string{ @@ -155,7 +155,7 @@ func TestPluginReadCommand_Run(t *testing.T) { t.Run("no_tabs", func(t *testing.T) { t.Parallel() - _, cmd := testPluginReadCommand(t) + _, cmd := testPluginInfoCommand(t) assertNoTabs(t, cmd) }) } From cfa69392ffb69690fd3c44d8446623c91d5e2871 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 20:06:54 -0400 Subject: [PATCH 07/12] Add base plugin for help text --- command/commands.go | 5 +++++ command/plugin.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 command/plugin.go diff --git a/command/commands.go b/command/commands.go index eb377174ec0b..687b9dc2b710 100644 --- a/command/commands.go +++ b/command/commands.go @@ -361,6 +361,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { BaseCommand: getBaseCommand(), }, nil }, + "plugin": func() (cli.Command, error) { + return &PluginCommand{ + BaseCommand: getBaseCommand(), + }, nil + }, "plugin deregister": func() (cli.Command, error) { return &PluginDeregisterCommand{ BaseCommand: getBaseCommand(), diff --git a/command/plugin.go b/command/plugin.go new file mode 100644 index 000000000000..f0bd2fbac8f9 --- /dev/null +++ b/command/plugin.go @@ -0,0 +1,46 @@ +package command + +import ( + "strings" + + "github.com/mitchellh/cli" +) + +var _ cli.Command = (*PluginCommand)(nil) + +type PluginCommand struct { + *BaseCommand +} + +func (c *PluginCommand) Synopsis() string { + return "Interact with Vault plugins and catalog" +} + +func (c *PluginCommand) Help() string { + helpText := ` +Usage: vault plugin [options] [args] + + This command groups subcommands for interacting with Vault's plugins and the + plugin catalog. Here are a few examples of the plugin commands: + + List all available plugins in the catalog: + + $ vault plugin list + + Register a new plugin to the catalog: + + $ vault plugin register my-custom-plugin -sha256=d3f0a8b... + + Get information about a plugin in the catalog: + + $ vault plugin info my-custom-plugin + + Please see the individual subcommand help for detailed usage information. +` + + return strings.TrimSpace(helpText) +} + +func (c *PluginCommand) Run(args []string) int { + return cli.RunResultHelp +} From 77cad05e1cdd8449fe8990f8906df13efcad3eee Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 20:07:06 -0400 Subject: [PATCH 08/12] Fix arg ordering --- command/plugin_register.go | 12 ++++++------ command/plugin_register_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/command/plugin_register.go b/command/plugin_register.go index 3558cb576f5e..9bd5e76b33b1 100644 --- a/command/plugin_register.go +++ b/command/plugin_register.go @@ -33,14 +33,14 @@ Usage: vault plugin register [options] NAME Register the plugin named my-custom-plugin: - $ vault plugin register my-custom-plugin \ - -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 + $ vault plugin register -sha256=d3f0a8b... my-custom-plugin Register a plugin with custom arguments: - $ vault plugin register my-custom-plugin \ - -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 \ - -args=--with-glibc,--with-cgo + $ vault plugin register \ + -sha256=d3f0a8b... \ + -args=--with-glibc,--with-cgo \ + my-custom-plugin ` + c.Flags().Help() @@ -130,6 +130,6 @@ func (c *PluginRegisterCommand) Run(args []string) int { return 2 } - c.UI.Output(fmt.Sprintf("Success! Registered plugin %s", pluginName)) + c.UI.Output(fmt.Sprintf("Success! Registered plugin: %s", pluginName)) return 0 } diff --git a/command/plugin_register_test.go b/command/plugin_register_test.go index 2bd07aaf9d2a..aae490298f46 100644 --- a/command/plugin_register_test.go +++ b/command/plugin_register_test.go @@ -96,7 +96,7 @@ func TestPluginRegisterCommand_Run(t *testing.T) { t.Errorf("expected %d to be %d", code, exp) } - expected := "Success! Registered plugin my-plugin" + expected := "Success! Registered plugin: my-plugin" combined := ui.OutputWriter.String() + ui.ErrorWriter.String() if !strings.Contains(combined, expected) { t.Errorf("expected %q to contain %q", combined, expected) From 58f3014e92e44712f6655371a9af064913a29522 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Wed, 11 Jul 2018 20:07:14 -0400 Subject: [PATCH 09/12] Add docs --- website/source/docs/commands/plugin.html.md | 63 +++++++++++++++++++ .../docs/commands/plugin/deregister.html.md | 27 ++++++++ .../source/docs/commands/plugin/info.html.md | 43 +++++++++++++ .../source/docs/commands/plugin/list.html.md | 35 +++++++++++ .../docs/commands/plugin/register.html.md | 53 ++++++++++++++++ website/source/layouts/docs.erb | 17 +++++ 6 files changed, 238 insertions(+) create mode 100644 website/source/docs/commands/plugin.html.md create mode 100644 website/source/docs/commands/plugin/deregister.html.md create mode 100644 website/source/docs/commands/plugin/info.html.md create mode 100644 website/source/docs/commands/plugin/list.html.md create mode 100644 website/source/docs/commands/plugin/register.html.md diff --git a/website/source/docs/commands/plugin.html.md b/website/source/docs/commands/plugin.html.md new file mode 100644 index 000000000000..c86c06fb7c75 --- /dev/null +++ b/website/source/docs/commands/plugin.html.md @@ -0,0 +1,63 @@ +--- +layout: "docs" +page_title: "plugin - Command" +sidebar_current: "docs-commands-plugin" +description: |- + The "plugin" command groups subcommands for interacting with + Vault's plugins and the plugin catalog. +--- + +# plugin + +The `plugin` command groups subcommands for interacting with Vault's plugins and +the plugin catalog + +## Examples + +List all available plugins in the catalog: + +```text +$ vault plugin list + +Plugins +------- +my-custom-plugin +# ... +``` + +Register a new plugin to the catalog: + +```text +$ vault plugin register \ + -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 \ + my-custom-plugin +Success! Registered plugin: my-custom-plugin +``` + +Get information about a plugin in the catalog: + +```text +$ vault plugin info my-custom-plugin +Key Value +--- ----- +command my-custom-plugin +name my-custom-plugin +sha256 d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 +``` + +## Usage + +```text +Usage: vault plugin [options] [args] + + # ... + +Subcommands: + deregister Deregister an existing plugin in the catalog + list Lists available plugins + read Read information about a plugin in the catalog + register Registers a new plugin in the catalog +``` + +For more information, examples, and usage about a subcommand, click on the name +of the subcommand in the sidebar. diff --git a/website/source/docs/commands/plugin/deregister.html.md b/website/source/docs/commands/plugin/deregister.html.md new file mode 100644 index 000000000000..e49bf9a4b504 --- /dev/null +++ b/website/source/docs/commands/plugin/deregister.html.md @@ -0,0 +1,27 @@ +--- +layout: "docs" +page_title: "plugin deregister - Command" +sidebar_current: "docs-commands-plugin-deregister" +description: |- + The "plugin deregister" command deregisters a new plugin in Vault's plugin + catalog. +--- + +# plugin deregister + +The `plugin deregister` command deregisters an existing plugin from Vault's +plugin catalog. If the plugin does not exist, no error is returned. + +## Examples + +Deregister a plugin: + +```text +$ vault plugin deregister my-custom-plugin +Success! Deregistered plugin (if it was registered): my-custom-plugin +``` + +## Usage + +There are no flags beyond the [standard set of flags](/docs/commands/index.html) +included on all commands. diff --git a/website/source/docs/commands/plugin/info.html.md b/website/source/docs/commands/plugin/info.html.md new file mode 100644 index 000000000000..2e3284d095be --- /dev/null +++ b/website/source/docs/commands/plugin/info.html.md @@ -0,0 +1,43 @@ +--- +layout: "docs" +page_title: "plugin info - Command" +sidebar_current: "docs-commands-plugin-info" +description: |- + The "plugin info" command displays information about a plugin in the catalog. +--- + +# plugin info + +The `plugin info` displays information about a plugin in the catalog. + +## Examples + +Display information about a plugin + +```text +$ vault plugin info my-custom-plugin + +Key Value +--- ----- +args [] +builtin false +command my-custom-plugin +name my-custom-plugin +sha256 d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 +``` + +## Usage + +The following flags are available in addition to the [standard set of +flags](/docs/commands/index.html) included on all commands. + +### Output Options + +- `-field` `(string: "")` - Print only the field with the given name. Specifying + this option will take precedence over other formatting directives. The result + will not have a trailing newline making it ideal for piping to other + processes. + +- `-format` `(string: "table")` - Print the output in the given format. Valid + formats are "table", "json", or "yaml". This can also be specified via the + `VAULT_FORMAT` environment variable. diff --git a/website/source/docs/commands/plugin/list.html.md b/website/source/docs/commands/plugin/list.html.md new file mode 100644 index 000000000000..ab35ae5614f9 --- /dev/null +++ b/website/source/docs/commands/plugin/list.html.md @@ -0,0 +1,35 @@ +--- +layout: "docs" +page_title: "plugin list - Command" +sidebar_current: "docs-commands-plugin-list" +description: |- + The "plugin list" command lists all available plugins in the plugin catalog. +--- + +# plugin list + +The `plugin list` command lists all available plugins in the plugin catalog. + +## Examples + +List all available plugins in the catalog. + +```text +$ vault plugin list + +Plugins +------- +my-custom-plugin +# ... +``` + +## Usage + +The following flags are available in addition to the [standard set of +flags](/docs/commands/index.html) included on all commands. + +### Output Options + +- `-format` `(string: "table")` - Print the output in the given format. Valid + formats are "table", "json", or "yaml". This can also be specified via the + `VAULT_FORMAT` environment variable. diff --git a/website/source/docs/commands/plugin/register.html.md b/website/source/docs/commands/plugin/register.html.md new file mode 100644 index 000000000000..cde96f4d6ece --- /dev/null +++ b/website/source/docs/commands/plugin/register.html.md @@ -0,0 +1,53 @@ +--- +layout: "docs" +page_title: "plugin register - Command" +sidebar_current: "docs-commands-plugin-register" +description: |- + The "plugin register" command registers a new plugin in Vault's plugin + catalog. +--- + +# plugin register + +The `plugin register` command registers a new plugin in Vault's plugin catalog. + +## Examples + +Register a plugin: + +```text +$ vault plugin register \ + -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 \ + my-custom-plugin +Success! Registered plugin: my-custom-plugin +``` + +Register a plugin with custom args: + +```text +$ vault plugin register \ + -sha256=d3f0a8be02f6c074cf38c9c99d4d04c9c6466249 \ + -args=--with-glibc,--with-curl-bindings \ + my-custom-plugin +``` + +## Usage + +The following flags are available in addition to the [standard set of +flags](/docs/commands/index.html) included on all commands. + +### Output Options + +- `-format` `(string: "table")` - Print the output in the given format. Valid + formats are "table", "json", or "yaml". This can also be specified via the + `VAULT_FORMAT` environment variable. + +### Command Options + +- `-sha256` `(string: )` - Checksum (SHA256) of the plugin binary. + +- `-args` `(string: "")` - List of arguments to pass to the binary plugin during + each invocation. Specify multiple arguments with commas. + +- `-command` `(string: "")` - Name of the command to run to invoke the binary. + By default, this is the name of the plugin. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index ce08e994dac8..dcd210af0552 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -265,6 +265,23 @@ > path-help + > + plugin + + > policy