diff --git a/api/sys_mounts.go b/api/sys_mounts.go index 298a9ac8a4..422a90dccb 100644 --- a/api/sys_mounts.go +++ b/api/sys_mounts.go @@ -73,7 +73,9 @@ func (c *Sys) Remount(from, to string) error { } resp, err := c.c.RawRequest(r) - defer resp.Body.Close() + if err == nil { + defer resp.Body.Close() + } return err } diff --git a/command/remount.go b/command/remount.go new file mode 100644 index 0000000000..7ae84d16f8 --- /dev/null +++ b/command/remount.go @@ -0,0 +1,85 @@ +package command + +import ( + "fmt" + "strings" +) + +// RemountCommand is a Command that remounts a mounted secret backend +// to a new endpoint. +type RemountCommand struct { + Meta +} + +func (c *RemountCommand) Run(args []string) int { + flags := c.Meta.FlagSet("remount", FlagSetDefault) + flags.Usage = func() { c.Ui.Error(c.Help()) } + if err := flags.Parse(args); err != nil { + return 1 + } + + args = flags.Args() + if len(args) != 2 { + flags.Usage() + c.Ui.Error(fmt.Sprintf( + "\nRemount expects two arguments: the from and to path")) + return 1 + } + + from := args[0] + to := args[1] + + client, err := c.Client() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error initializing client: %s", err)) + return 2 + } + + if err := client.Sys().Remount(from, to); err != nil { + c.Ui.Error(fmt.Sprintf( + "Unmount error: %s", err)) + return 2 + } + + c.Ui.Output(fmt.Sprintf( + "Successfully remounted from '%s' to '%s'!", from, to)) + + return 0 +} + +func (c *RemountCommand) Synopsis() string { + return "Remount a secret backend to a new path" +} + +func (c *RemountCommand) Help() string { + helpText := ` +Usage: vault remount [options] from to + + Remount a mounted secret backend to a new path. + + This command remounts a secret backend that is already mounted to + a new path. All the secrets from the old path will be revoked, but + the Vault data associated with the backend will be preserved (such + as configuration data). + + Example: vault remount secret/ generic/ + +General Options: + + -address=TODO The address of the Vault server. + + -ca-cert=path Path to a PEM encoded CA cert file to use to + verify the Vault server SSL certificate. + + -ca-path=path Path to a directory of PEM encoded CA cert files + to verify the Vault server SSL certificate. If both + -ca-cert and -ca-path are specified, -ca-path is used. + + -insecure Do not verify TLS certificate. This is highly + not recommended. This is especially not recommended + for unsealing a vault. + +` + return strings.TrimSpace(helpText) +} diff --git a/command/remount_test.go b/command/remount_test.go new file mode 100644 index 0000000000..5de9ce7fa5 --- /dev/null +++ b/command/remount_test.go @@ -0,0 +1,51 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/vault/http" + "github.com/hashicorp/vault/vault" + "github.com/mitchellh/cli" +) + +func TestRemount(t *testing.T) { + core, _, token := vault.TestCoreUnsealed(t) + ln, addr := http.TestServer(t, core) + defer ln.Close() + + ui := new(cli.MockUi) + c := &RemountCommand{ + Meta: Meta{ + ClientToken: token, + Ui: ui, + }, + } + + args := []string{ + "-address", addr, + "secret/", "generic", + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + client, err := c.Client() + if err != nil { + t.Fatalf("err: %s", err) + } + + mounts, err := client.Sys().ListMounts() + if err != nil { + t.Fatalf("err: %s", err) + } + + _, ok := mounts["secret/"] + if ok { + t.Fatal("should not have mount") + } + + _, ok = mounts["generic/"] + if !ok { + t.Fatal("should have generic") + } +} diff --git a/commands.go b/commands.go index c74bc6fecc..7694ce4304 100644 --- a/commands.go +++ b/commands.go @@ -141,6 +141,12 @@ func init() { }, nil }, + "remount": func() (cli.Command, error) { + return &command.RemountCommand{ + Meta: meta, + }, nil + }, + "unmount": func() (cli.Command, error) { return &command.UnmountCommand{ Meta: meta,