diff --git a/command/policy_delete.go b/command/policy_delete.go index ff8342a625..a2bbc25bda 100644 --- a/command/policy_delete.go +++ b/command/policy_delete.go @@ -4,62 +4,84 @@ import ( "fmt" "strings" - "github.com/hashicorp/vault/meta" + "github.com/mitchellh/cli" + "github.com/posener/complete" ) +// Ensure we are implementing the right interfaces. +var _ cli.Command = (*PolicyDeleteCommand)(nil) +var _ cli.CommandAutocomplete = (*PolicyDeleteCommand)(nil) + // PolicyDeleteCommand is a Command that enables a new endpoint. type PolicyDeleteCommand struct { - meta.Meta + *BaseCommand +} + +func (c *PolicyDeleteCommand) Synopsis() string { + return "Deletes a policy by name" +} + +func (c *PolicyDeleteCommand) Help() string { + helpText := ` +Usage: vault policy-delete [options] NAME + + Deletes a policy in the Vault server with the given name. Once the policy + is deleted, all tokens associated with the policy will be affected + immediately. + + Delete the policy named "my-policy": + + $ vault policy-delete my-policy + + For a full list of examples, please see the documentation. + +` + c.Flags().Help() + + return strings.TrimSpace(helpText) +} + +func (c *PolicyDeleteCommand) Flags() *FlagSets { + return c.flagSet(FlagSetHTTP) +} + +func (c *PolicyDeleteCommand) AutocompleteArgs() complete.Predictor { + return c.PredictVaultPolicies() +} + +func (c *PolicyDeleteCommand) AutocompleteFlags() complete.Flags { + return c.Flags().Completions() } func (c *PolicyDeleteCommand) Run(args []string) int { - flags := c.Meta.FlagSet("policy-delete", meta.FlagSetDefault) - flags.Usage = func() { c.Ui.Error(c.Help()) } - if err := flags.Parse(args); err != nil { + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.UI.Error(err.Error()) return 1 } - args = flags.Args() - if len(args) != 1 { - flags.Usage() - c.Ui.Error(fmt.Sprintf( - "\npolicy-delete expects exactly one argument")) + 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(fmt.Sprintf( - "Error initializing client: %s", err)) + c.UI.Error(err.Error()) return 2 } - name := args[0] + name := strings.TrimSpace(strings.ToLower(args[0])) if err := client.Sys().DeletePolicy(name); err != nil { - c.Ui.Error(fmt.Sprintf( - "Error: %s", err)) - return 1 + c.UI.Error(fmt.Sprintf("Error deleting %s: %s", name, err)) + return 2 } - c.Ui.Output(fmt.Sprintf("Policy '%s' deleted.", name)) + c.UI.Output(fmt.Sprintf("Success! Deleted policy: %s", name)) return 0 } - -func (c *PolicyDeleteCommand) Synopsis() string { - return "Delete a policy from the server" -} - -func (c *PolicyDeleteCommand) Help() string { - helpText := ` -Usage: vault policy-delete [options] name - - Delete a policy with the given name. - - Once the policy is deleted, all users associated with the policy will - be affected immediately. When a user is associated with a policy that - doesn't exist, it is identical to not being associated with that policy. - -General Options: -` + meta.GeneralOptionsUsage() - return strings.TrimSpace(helpText) -} diff --git a/command/policy_delete_test.go b/command/policy_delete_test.go index 4f62a1028d..1c30c739d7 100644 --- a/command/policy_delete_test.go +++ b/command/policy_delete_test.go @@ -1,61 +1,136 @@ package command import ( + "reflect" + "strings" "testing" - "github.com/hashicorp/vault/http" - "github.com/hashicorp/vault/meta" - "github.com/hashicorp/vault/vault" "github.com/mitchellh/cli" ) -func TestPolicyDelete(t *testing.T) { - core, _, token := vault.TestCoreUnsealed(t) - ln, addr := http.TestServer(t, core) - defer ln.Close() +func testPolicyDeleteCommand(tb testing.TB) (*cli.MockUi, *PolicyDeleteCommand) { + tb.Helper() - ui := new(cli.MockUi) - c := &PolicyDeleteCommand{ - Meta: meta.Meta{ - ClientToken: token, - Ui: ui, + ui := cli.NewMockUi() + return ui, &PolicyDeleteCommand{ + BaseCommand: &BaseCommand{ + UI: ui, + }, + } +} + +func TestPolicyDeleteCommand_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, }, } - args := []string{ - "-address", addr, - "foo", - } + t.Run("validations", func(t *testing.T) { + t.Parallel() - // Run once so the client is setup, ignore errors - c.Run(args) + for _, tc := range cases { + tc := tc - // Get the client so we can write data - client, err := c.Client() - if err != nil { - t.Fatalf("err: %s", err) - } - if err := client.Sys().PutPolicy("foo", testPolicyDeleteRules); err != nil { - t.Fatalf("err: %s", err) - } + t.Run(tc.name, func(t *testing.T) { + t.Parallel() - // Test that the delete works - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } + ui, cmd := testPolicyDeleteCommand(t) - // Test the policy is gone - rules, err := client.Sys().GetPolicy("foo") - if err != nil { - t.Fatalf("err: %s", err) - } - if rules != "" { - t.Fatalf("bad: %#v", rules) - } + 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() + + policy := `path "secret/" {}` + if err := client.Sys().PutPolicy("my-policy", policy); err != nil { + t.Fatal(err) + } + + ui, cmd := testPolicyDeleteCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "my-policy", + }) + if exp := 0; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Success! Deleted policy: my-policy" + combined := ui.OutputWriter.String() + ui.ErrorWriter.String() + if !strings.Contains(combined, expected) { + t.Errorf("expected %q to contain %q", combined, expected) + } + + policies, err := client.Sys().ListPolicies() + if err != nil { + t.Fatal(err) + } + + list := []string{"default", "root"} + if !reflect.DeepEqual(policies, list) { + t.Errorf("expected %q to be %q", policies, list) + } + }) + + t.Run("communication_failure", func(t *testing.T) { + t.Parallel() + + client, closer := testVaultServerBad(t) + defer closer() + + ui, cmd := testPolicyDeleteCommand(t) + cmd.client = client + + code := cmd.Run([]string{ + "my-policy", + }) + if exp := 2; code != exp { + t.Errorf("expected %d to be %d", code, exp) + } + + expected := "Error deleting my-policy: " + 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 := testPolicyDeleteCommand(t) + assertNoTabs(t, cmd) + }) } - -const testPolicyDeleteRules = ` -path "sys" { - policy = "deny" -} -`