-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
80a0d56
commit c50881b
Showing
19 changed files
with
1,382 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} |
Oops, something went wrong.