Update delete command

This commit is contained in:
Seth Vargo
2017-09-05 00:00:06 -04:00
parent a7589f7613
commit d38abb665b
2 changed files with 187 additions and 83 deletions

View File

@@ -4,64 +4,93 @@ 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 = (*DeleteCommand)(nil)
var _ cli.CommandAutocomplete = (*DeleteCommand)(nil)
// DeleteCommand is a Command that puts data into the Vault. // DeleteCommand is a Command that puts data into the Vault.
type DeleteCommand struct { type DeleteCommand struct {
meta.Meta *BaseCommand
}
func (c *DeleteCommand) Run(args []string) int {
flags := c.Meta.FlagSet("delete", 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 {
c.Ui.Error("delete expects one argument")
flags.Usage()
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.Logical().Delete(path); err != nil {
c.Ui.Error(fmt.Sprintf(
"Error deleting '%s': %s", path, err))
return 1
}
c.Ui.Output(fmt.Sprintf("Success! Deleted '%s' if it existed.", path))
return 0
} }
func (c *DeleteCommand) Synopsis() string { func (c *DeleteCommand) Synopsis() string {
return "Delete operation on secrets in Vault" return "Deletes secrets and configuration"
} }
func (c *DeleteCommand) Help() string { func (c *DeleteCommand) Help() string {
helpText := ` helpText := `
Usage: vault delete [options] path Usage: vault delete [options] PATH
Delete data (secrets or configuration) from Vault. Deletes secrets and configuration from Vault at the given path. The behavior
of "delete" is delegated to the backend corresponding to the given path.
Delete sends a delete operation request to the given path. The Remove data in the status secret backend:
behavior of the delete is determined by the backend at the given
path. For example, deleting "aws/policy/ops" will delete the "ops" $ vault delete secret/my-secret
policy for the AWS backend. Use "vault help" for more details on
whether delete is supported for a path and what the behavior is. Uninstall an encryption key in the transit backend:
$ vault delete transit/keys/my-key
Delete an IAM role:
$ vault delete aws/roles/ops
For a full list of examples and paths, please see the documentation that
corresponds to the secret backend in use.
` + c.Flags().Help()
General Options:
` + meta.GeneralOptionsUsage()
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *DeleteCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP)
}
func (c *DeleteCommand) AutocompleteArgs() complete.Predictor {
return c.PredictVaultFiles()
}
func (c *DeleteCommand) AutocompleteFlags() complete.Flags {
return c.Flags().Completions()
}
func (c *DeleteCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
path, kvs, err := extractPath(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if len(kvs) > 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.Logical().Delete(path); err != nil {
c.UI.Error(fmt.Sprintf("Error deleting %s: %s", path, err))
return 2
}
c.UI.Info(fmt.Sprintf("Success! Data deleted (if it existed) at: %s", path))
return 0
}

View File

@@ -1,56 +1,131 @@
package command package command
import ( import (
"strings"
"testing" "testing"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
func TestDelete(t *testing.T) { func testDeleteCommand(tb testing.TB) (*cli.MockUi, *DeleteCommand) {
core, _, token := vault.TestCoreUnsealed(t) tb.Helper()
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi) ui := cli.NewMockUi()
c := &DeleteCommand{ return ui, &DeleteCommand{
Meta: meta.Meta{ BaseCommand: &BaseCommand{
ClientToken: token, UI: ui,
Ui: ui, },
}
}
func TestDeleteCommand_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,
}, },
} }
args := []string{ t.Run("validations", func(t *testing.T) {
"-address", addr, t.Parallel()
"secret/foo",
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
ui, cmd := testDeleteCommand(t)
code := cmd.Run(tc.args)
if code != tc.code {
t.Errorf("expected %d to be %d", code, tc.code)
} }
// Run once so the client is setup, ignore errors combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
c.Run(args) if !strings.Contains(combined, tc.out) {
t.Errorf("expected %q to contain %q", combined, tc.out)
}
})
}
})
// Get the client so we can write data t.Run("integration", func(t *testing.T) {
client, err := c.Client() t.Parallel()
if err != nil {
t.Fatalf("err: %s", err) client, closer := testVaultServer(t)
defer closer()
if _, err := client.Logical().Write("secret/delete/foo", map[string]interface{}{
"foo": "bar",
}); err != nil {
t.Fatal(err)
} }
data := map[string]interface{}{"value": "bar"} ui, cmd := testDeleteCommand(t)
if _, err := client.Logical().Write("secret/foo", data); err != nil { cmd.client = client
t.Fatalf("err: %s", err)
code := cmd.Run([]string{
"secret/delete/foo",
})
if exp := 0; code != exp {
t.Errorf("expected %d to be %d", code, exp)
} }
// Run the delete expected := "Success! Data deleted (if it existed) at: secret/delete/foo"
if code := c.Run(args); code != 0 { combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) if !strings.Contains(combined, expected) {
t.Errorf("expected %q to contain %q", combined, expected)
} }
resp, err := client.Logical().Read("secret/foo") secret, _ := client.Logical().Read("secret/delete/foo")
if err != nil { if secret != nil {
t.Fatalf("err: %s", err) t.Errorf("expected deletion: %#v", secret)
} }
if resp != nil { })
t.Fatalf("bad: %#v", resp)
t.Run("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testDeleteCommand(t)
cmd.client = client
code := cmd.Run([]string{
"secret/delete/foo",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
} }
expected := "Error deleting secret/delete/foo: "
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 := testDeleteCommand(t)
assertNoTabs(t, cmd)
})
} }