Add plugin CLI for interacting with the plugin catalog (#4911)
* Add 'plugin list' command * Add 'plugin register' command * Add 'plugin deregister' command * Use a shared plugin helper * Add 'plugin read' command * Rename to plugin info * Add base plugin for help text * Fix arg ordering * Add docs * Rearrange to alphabetize * Fix arg ordering in example * Don't use "sudo" in command description
This commit is contained in:
parent
51d842bd0a
commit
1259ee6743
|
@ -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.
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -361,6 +361,31 @@ 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(),
|
||||
}, nil
|
||||
},
|
||||
"plugin info": func() (cli.Command, error) {
|
||||
return &PluginInfoCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin list": func() (cli.Command, error) {
|
||||
return &PluginListCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin register": func() (cli.Command, error) {
|
||||
return &PluginRegisterCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"policy": func() (cli.Command, error) {
|
||||
return &PolicyCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
|
|
|
@ -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 <subcommand> [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 -sha256=d3f0a8b... my-custom-plugin
|
||||
|
||||
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
|
||||
}
|
|
@ -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. If the plugin does not exist,
|
||||
no action is taken (the command is idempotent).
|
||||
|
||||
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 named %s: %s", pluginName, err))
|
||||
return 2
|
||||
}
|
||||
|
||||
c.UI.Output(fmt.Sprintf("Success! Deregistered plugin (if it was registered): %s", pluginName))
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"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()
|
||||
|
||||
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 := testPluginDeregisterCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
|
||||
Name: pluginName,
|
||||
Command: pluginName,
|
||||
SHA256: sha256Sum,
|
||||
}); 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 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 := testPluginDeregisterCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*PluginInfoCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*PluginInfoCommand)(nil)
|
||||
|
||||
type PluginInfoCommand struct {
|
||||
*BaseCommand
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) Synopsis() string {
|
||||
return "Read information about a plugin in the catalog"
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin info [options] NAME
|
||||
|
||||
Displays information about a plugin in the catalog with the given name. If
|
||||
the plugin does not exist, an error is returned.
|
||||
|
||||
Get info about a plugin:
|
||||
|
||||
$ vault plugin info mysql-database-plugin
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) Flags() *FlagSets {
|
||||
return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) AutocompleteArgs() complete.Predictor {
|
||||
return c.PredictVaultPlugins()
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginInfoCommand) 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)
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginInfoCommand(tb testing.TB) (*cli.MockUi, *PluginInfoCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginInfoCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginInfoCommand_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 := testPluginInfoCommand(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 := testPluginInfoCommand(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 := testPluginInfoCommand(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 := testPluginInfoCommand(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 := testPluginInfoCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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 -sha256=d3f0a8b... my-custom-plugin
|
||||
|
||||
Register a plugin with custom arguments:
|
||||
|
||||
$ vault plugin register \
|
||||
-sha256=d3f0a8b... \
|
||||
-args=--with-glibc,--with-cgo \
|
||||
my-custom-plugin
|
||||
|
||||
` + 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
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"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()
|
||||
|
||||
pluginDir, cleanup := testPluginDir(t)
|
||||
defer cleanup(t)
|
||||
|
||||
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", sha256Sum,
|
||||
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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 <subcommand> [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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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: <required>)` - 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.
|
|
@ -265,6 +265,23 @@
|
|||
<li<%= sidebar_current("docs-commands-path-help") %>>
|
||||
<a href="/docs/commands/path-help.html">path-help</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-plugin") %>>
|
||||
<a href="/docs/commands/plugin.html">plugin</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-commands-plugin-deregister") %>>
|
||||
<a href="/docs/commands/plugin/deregister.html">deregister</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-plugin-info") %>>
|
||||
<a href="/docs/commands/plugin/info.html">info</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-plugin-list") %>>
|
||||
<a href="/docs/commands/plugin/list.html">list</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-plugin-register") %>>
|
||||
<a href="/docs/commands/plugin/register.html">register</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-commands-policy") %>>
|
||||
<a href="/docs/commands/policy.html">policy</a>
|
||||
<ul class="nav">
|
||||
|
|
Loading…
Reference in New Issue