Added plugin reload function to api (#8777)
* Added plugin reload function to api * Apply suggestions from code review Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * go mod vendor * addressing comments * addressing comments * add docs Co-authored-by: Calvin Leung Huang <cleung2010@gmail.com>
This commit is contained in:
parent
acd7bfb83e
commit
34f01920e9
|
@ -225,6 +225,35 @@ func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// ReloadPluginInput is used as input to the ReloadPlugin function.
|
||||
type ReloadPluginInput struct {
|
||||
// Plugin is the name of the plugin to reload, as registered in the plugin catalog
|
||||
Plugin string `json:"plugin"`
|
||||
|
||||
// Mounts is the array of string mount paths of the plugin backends to reload
|
||||
Mounts []string `json:"mounts"`
|
||||
}
|
||||
|
||||
// ReloadPlugin reloads mounted plugin backends
|
||||
func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error {
|
||||
path := "/v1/sys/plugins/reload/backend"
|
||||
req := c.c.NewRequest(http.MethodPut, path)
|
||||
|
||||
if err := req.SetJSONBody(i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
resp, err := c.c.RawRequestWithContext(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// catalogPathByType is a helper to construct the proper API path by plugin type
|
||||
func catalogPathByType(pluginType consts.PluginType, name string) string {
|
||||
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
|
||||
|
|
|
@ -442,6 +442,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
|||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"plugin reload": func() (cli.Command, error) {
|
||||
return &PluginReloadCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
}, nil
|
||||
},
|
||||
"policy": func() (cli.Command, error) {
|
||||
return &PolicyCommand{
|
||||
BaseCommand: getBaseCommand(),
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
var _ cli.Command = (*PluginReloadCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*PluginReloadCommand)(nil)
|
||||
|
||||
type PluginReloadCommand struct {
|
||||
*BaseCommand
|
||||
plugin string
|
||||
mounts []string
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) Synopsis() string {
|
||||
return "Reload mounted plugin backend"
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault plugin reload [options]
|
||||
|
||||
Reloads mounted plugins. Either the plugin name or the desired plugin
|
||||
mount(s) must be provided, but not both. In case the plugin name is provided,
|
||||
all of its corresponding mounted paths that use the plugin backend will be reloaded.
|
||||
|
||||
Reload the plugin named "my-custom-plugin":
|
||||
|
||||
$ vault plugin reload -plugin=my-custom-plugin
|
||||
|
||||
` + c.Flags().Help()
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "plugin",
|
||||
Target: &c.plugin,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "The name of the plugin to reload, as registered in the plugin catalog.",
|
||||
})
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "mounts",
|
||||
Target: &c.mounts,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Array or comma-separated string mount paths of the plugin backends to reload.",
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *PluginReloadCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
switch {
|
||||
case c.plugin == "" && len(c.mounts) == 0:
|
||||
c.UI.Error(fmt.Sprintf("Not enough arguments (expected 1, got %d)", len(args)))
|
||||
return 1
|
||||
case c.plugin != "" && len(c.mounts) > 0:
|
||||
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
|
||||
}
|
||||
|
||||
if err := client.Sys().ReloadPlugin(&api.ReloadPluginInput{
|
||||
Plugin: c.plugin,
|
||||
Mounts: c.mounts,
|
||||
}); err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error reloading plugin/mounts: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if len(c.mounts) > 0 {
|
||||
c.UI.Output(fmt.Sprintf("Success! Reloaded mounts: %s", c.mounts))
|
||||
} else {
|
||||
c.UI.Output(fmt.Sprintf("Success! Reloaded plugin: %s", c.plugin))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func testPluginReloadCommand(tb testing.TB) (*cli.MockUi, *PluginReloadCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &PluginReloadCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginReloadCommand_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{"-plugin", "foo", "-mounts", "bar"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testPluginReloadCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
args := append([]string{}, 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 := testPluginCreateAndRegister(t, client, pluginDir, pluginName, consts.PluginTypeCredential)
|
||||
|
||||
ui, cmd := testPluginReloadCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
if err := client.Sys().RegisterPlugin(&api.RegisterPluginInput{
|
||||
Name: pluginName,
|
||||
Type: consts.PluginTypeCredential,
|
||||
Command: pluginName,
|
||||
SHA256: sha256Sum,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-plugin", pluginName,
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Success! Reloaded plugin: "
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
|
@ -225,6 +225,34 @@ func (c *Sys) DeregisterPlugin(i *DeregisterPluginInput) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// ReloadPluginInput is used as input fo the ReloadPlugin function.
|
||||
type ReloadPluginInput struct {
|
||||
// Plugin is the name of the plugin to reload, as registered in the plugin catalog
|
||||
Plugin string `json:"plugin"`
|
||||
|
||||
// Mounts is the array of string mount paths of the plugin backends to reload
|
||||
Mounts []string `json:"mounts"`
|
||||
}
|
||||
|
||||
// ReloadPlugin reloads mounted plugin backends
|
||||
func (c *Sys) ReloadPlugin(i *ReloadPluginInput) error {
|
||||
path := "/v1/sys/plugins/reload/backend"
|
||||
req := c.c.NewRequest(http.MethodPut, path)
|
||||
|
||||
if err := req.SetJSONBody(i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
|
||||
resp, err := c.c.RawRequestWithContext(ctx, req)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// catalogPathByType is a helper to construct the proper API path by plugin type
|
||||
func catalogPathByType(pluginType consts.PluginType, name string) string {
|
||||
path := fmt.Sprintf("/v1/sys/plugins/catalog/%s/%s", pluginType, name)
|
||||
|
|
|
@ -54,9 +54,10 @@ Usage: vault plugin <subcommand> [options] [args]
|
|||
|
||||
Subcommands:
|
||||
deregister Deregister an existing plugin in the catalog
|
||||
info Read information about a 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
|
||||
reload Reload mounted plugin backend
|
||||
```
|
||||
|
||||
For more information, examples, and usage about a subcommand, click on the name
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
layout: docs
|
||||
page_title: plugin reload - Command
|
||||
sidebar_title: <code>reload</code>
|
||||
description: |-
|
||||
The "plugin reload" command reloads mounted plugins.
|
||||
---
|
||||
|
||||
# plugin reload
|
||||
|
||||
The `plugin reload` command is used to reload mounted plugin backends. Either
|
||||
the plugin name (`plugin`) or the desired plugin backend mounts (`mounts`)
|
||||
must be provided, but not both.
|
||||
|
||||
## Examples
|
||||
|
||||
Reload a plugin:
|
||||
|
||||
```text
|
||||
$ vault plugin reload -plugin my-custom-plugin
|
||||
Success! Reloaded plugin: my-custom-plugin
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The following flags are available in addition to the [standard set of
|
||||
flags](/docs/commands) included on all commands.
|
||||
|
||||
### Command Options
|
||||
|
||||
- `-plugin` `(string: "")` - The name of the plugin to reload, as registered in
|
||||
the plugin catalog.
|
||||
|
||||
- `-mounts` `(array: [])` - Array or comma-separated string mount paths of the
|
||||
plugin backends to reload.
|
Loading…
Reference in New Issue