Update unmount command

This commit is contained in:
Seth Vargo
2017-09-05 00:05:20 -04:00
parent 621774e425
commit a84b6e4173
2 changed files with 208 additions and 76 deletions

View File

@@ -4,64 +4,91 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/vault/meta" "github.com/mitchellh/cli"
"github.com/posener/complete"
) )
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*UnmountCommand)(nil)
var _ cli.CommandAutocomplete = (*UnmountCommand)(nil)
// UnmountCommand is a Command that mounts a new mount. // UnmountCommand is a Command that mounts a new mount.
type UnmountCommand struct { type UnmountCommand struct {
meta.Meta *BaseCommand
}
func (c *UnmountCommand) Run(args []string) int {
flags := c.Meta.FlagSet("mount", meta.FlagSetDefault)
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
args = flags.Args()
if len(args) != 1 {
flags.Usage()
c.Ui.Error(fmt.Sprintf(
"\nunmount expects one argument: the path to unmount"))
return 1
}
path := args[0]
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
return 2
}
if err := client.Sys().Unmount(path); err != nil {
c.Ui.Error(fmt.Sprintf(
"Unmount error: %s", err))
return 2
}
c.Ui.Output(fmt.Sprintf(
"Successfully unmounted '%s' if it was mounted", path))
return 0
} }
func (c *UnmountCommand) Synopsis() string { func (c *UnmountCommand) Synopsis() string {
return "Unmount a secret backend" return "Unmounts a secret backend"
} }
func (c *UnmountCommand) Help() string { func (c *UnmountCommand) Help() string {
helpText := ` helpText := `
Usage: vault unmount [options] path Usage: vault unmount [options] PATH
Unmount a secret backend. Unmounts a secret backend at the given PATH. The argument corresponds to
the PATH of the mount, not the TYPE! All secrets created by this backend
are revoked and its Vault data is removed.
This command unmounts a secret backend. All the secrets created If no mount exists at the given path, the command will still return as
by this backend will be revoked and its Vault data will be deleted. successful because unmounting is an idempotent operation.
Unmount the secret backend mounted at aws/:
$ vault unmount aws/
For a full list of examples, please see the documentation.
` + c.Flags().Help()
General Options:
` + meta.GeneralOptionsUsage()
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *UnmountCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *UnmountCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultMounts()
}
func (c *UnmountCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *UnmountCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
mountPath, remaining, err := extractPath(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if len(remaining) > 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
}
// Append a trailing slash to indicate it's a path in output
mountPath = ensureTrailingSlash(mountPath)
if err := client.Sys().Unmount(mountPath); err != nil {
c.UI.Error(fmt.Sprintf("Error unmounting %s: %s", mountPath, err))
return 2
}
c.UI.Output(fmt.Sprintf("Success! Unmounted the secret backend (if it existed) at: %s", mountPath))
return 0
}

View File

@@ -1,47 +1,152 @@
package command package command
import ( import (
"strings"
"testing" "testing"
"github.com/hashicorp/vault/http" "github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestUnmount(t *testing.T) { func testUnmountCommand(tb testing.TB) (*cli.MockUi, *UnmountCommand) {
core, _, token := vault.TestCoreUnsealed(t) tb.Helper()
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi) ui := cli.NewMockUi()
c := &UnmountCommand{ return ui, &UnmountCommand{
Meta: meta.Meta{ BaseCommand: &BaseCommand{
ClientToken: token, UI: ui,
Ui: ui, },
}
}
func TestUnmountCommand_Run(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
out string
code int
}{
{
"empty",
nil,
"Missing PATH!",
1,
},
{
"slash",
[]string{"/"},
"Missing PATH!",
1,
},
{
"not_real",
[]string{"not_real"},
"Success! Unmounted the secret backend (if it existed) at: not_real/",
0,
},
{
"default",
[]string{"secret"},
"Success! Unmounted the secret backend (if it existed) at: secret/",
0,
}, },
} }
args := []string{ t.Run("validations", func(t *testing.T) {
"-address", addr, t.Parallel()
"secret",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
client, err := c.Client() for _, tc := range cases {
if err != nil { tc := tc
t.Fatalf("err: %s", err)
}
mounts, err := client.Sys().ListMounts() t.Run(tc.name, func(t *testing.T) {
if err != nil { t.Parallel()
t.Fatalf("err: %s", err)
}
_, ok := mounts["secret/"] client, closer := testVaultServer(t)
if ok { defer closer()
t.Fatal("should not have mount")
} ui, cmd := testUnmountCommand(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()
client, closer := testVaultServer(t)
defer closer()
if err := client.Sys().Mount("integration_unmount/", &api.MountInput{
Type: "generic",
}); err != nil {
t.Fatal(err)
}
ui, cmd := testUnmountCommand(t)
cmd.client = client
code := cmd.Run([]string{
"integration_unmount/",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Success! Unmounted the secret backend (if it existed) at: integration_unmount/"
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
}
mounts, err := client.Sys().ListMounts()
if err != nil {
t.Fatal(err)
}
if _, ok := mounts["integration_unmount"]; ok {
t.Errorf("expected mount to not exist: %#v", mounts)
}
})
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testUnmountCommand(t)
cmd.client = client
code := cmd.Run([]string{
"pki/",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error unmounting pki/: "
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 := testUnmountCommand(t)
assertNoTabs(t, cmd)
})
} }