mirror of https://github.com/hashicorp/consul
parent
82bddd7f9b
commit
297a22383f
@ -0,0 +1,178 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KVDeleteCommand is a Command implementation that is used to setup
|
||||||
|
// a "watch" which uses a sub-process
|
||||||
|
type KVDeleteCommand struct {
|
||||||
|
Ui cli.Ui
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KVDeleteCommand) Help() string {
|
||||||
|
helpText := `
|
||||||
|
Usage: consul kv delete [options] KEY_OR_PREFIX
|
||||||
|
|
||||||
|
Removes the value from Consul's key-value store at the given path. If no
|
||||||
|
key exists at the path, no action is taken.
|
||||||
|
|
||||||
|
To delete the value for the key named "foo" in the key-value store:
|
||||||
|
|
||||||
|
$ consul kv delete foo
|
||||||
|
|
||||||
|
To delete all keys which start with "foo", specify the -recurse option:
|
||||||
|
|
||||||
|
$ consul kv delete -recurse foo
|
||||||
|
|
||||||
|
This will delete the keys named "foo", "food", and "foo/bar/zip" if they
|
||||||
|
existed.
|
||||||
|
|
||||||
|
` + apiOptsText + `
|
||||||
|
|
||||||
|
KV Delete Options:
|
||||||
|
|
||||||
|
-cas Perform a Check-And-Set operation. If this value is
|
||||||
|
specified without -modify-index, the key will first be
|
||||||
|
fetched and the resulting ModifyIndex will be used on
|
||||||
|
the next query. The default value is false.
|
||||||
|
|
||||||
|
-modify-index=<int> Unsigned integer representing the ModifyIndex of the
|
||||||
|
key. This is often combined with the -cas flag, but it
|
||||||
|
can be specified for any key. The default value is 0.
|
||||||
|
|
||||||
|
-recurse Recursively delete all keys with the path. The default
|
||||||
|
value is false.
|
||||||
|
`
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KVDeleteCommand) Run(args []string) int {
|
||||||
|
cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError)
|
||||||
|
cmdFlags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||||
|
datacenter := cmdFlags.String("datacenter", "", "")
|
||||||
|
token := cmdFlags.String("token", "", "")
|
||||||
|
stale := cmdFlags.Bool("stale", false, "")
|
||||||
|
cas := cmdFlags.Bool("cas", false, "")
|
||||||
|
modifyIndex := cmdFlags.Uint64("modify-index", 0, "")
|
||||||
|
recurse := cmdFlags.Bool("recurse", false, "")
|
||||||
|
httpAddr := HTTPAddrFlag(cmdFlags)
|
||||||
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ""
|
||||||
|
|
||||||
|
// Check for arg validation
|
||||||
|
args = cmdFlags.Args()
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
key = ""
|
||||||
|
case 1:
|
||||||
|
key = args[0]
|
||||||
|
default:
|
||||||
|
c.Ui.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a "nice" thing to do. Since pairs cannot start with a /, but
|
||||||
|
// users will likely put "/" or "/foo", lets go ahead and strip that for them
|
||||||
|
// here.
|
||||||
|
if len(key) > 0 && key[0] == '/' {
|
||||||
|
key = key[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key is empty and we are not doing a recursive delete, this is an
|
||||||
|
// error.
|
||||||
|
if key == "" && !*recurse {
|
||||||
|
c.Ui.Error("Error! Missing KEY argument")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not valid to use a CAS and recurse in the same call
|
||||||
|
if *recurse && *cas {
|
||||||
|
c.Ui.Error("Cannot specify both -cas and -recurse!")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if *recurse && *modifyIndex != 0 {
|
||||||
|
c.Ui.Error("Cannot specify both -modify-index and -recurse!")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and test the HTTP client
|
||||||
|
conf := api.DefaultConfig()
|
||||||
|
conf.Address = *httpAddr
|
||||||
|
conf.Token = *token
|
||||||
|
client, err := api.NewClient(conf)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wo := &api.WriteOptions{
|
||||||
|
Datacenter: *datacenter,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case *recurse:
|
||||||
|
if _, err := client.KV().DeleteTree(key, wo); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error! Did not delete prefix %s: %s", key, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Info(fmt.Sprintf("Success! Deleted keys with prefix: %s", key))
|
||||||
|
return 0
|
||||||
|
case *cas:
|
||||||
|
pair := &api.KVPair{
|
||||||
|
Key: key,
|
||||||
|
ModifyIndex: *modifyIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user did not supply a -modify-index, but wants a check-and-set,
|
||||||
|
// grab the current modify index and store that on the key.
|
||||||
|
if pair.ModifyIndex == 0 {
|
||||||
|
currentPair, _, err := client.KV().Get(key, &api.QueryOptions{
|
||||||
|
Datacenter: *datacenter,
|
||||||
|
Token: *token,
|
||||||
|
AllowStale: *stale,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error! Could not get current key: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if currentPair != nil {
|
||||||
|
pair.ModifyIndex = currentPair.ModifyIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
success, _, err := client.KV().DeleteCAS(pair, wo)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error! Did not delete key %s: %s", key, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error! Did not delete key %s: CAS failed", key))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Info(fmt.Sprintf("Success! Deleted key: %s", key))
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
if _, err := client.KV().Delete(key, wo); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error deleting key %s: %s", key, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Info(fmt.Sprintf("Success! Deleted key: %s", key))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KVDeleteCommand) Synopsis() string {
|
||||||
|
return "Removes data from the KV store"
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKVDeleteCommand_implements(t *testing.T) {
|
||||||
|
var _ cli.Command = &KVDeleteCommand{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKVDeleteCommand_noTabs(t *testing.T) {
|
||||||
|
assertNoTabs(t, new(KVDeleteCommand))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKVDeleteCommand_Validation(t *testing.T) {
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &KVDeleteCommand{Ui: ui}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
args []string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
"-cas and -recurse": {
|
||||||
|
[]string{"-cas", "-recurse"},
|
||||||
|
"Cannot specify both",
|
||||||
|
},
|
||||||
|
"-modify-index and -recurse": {
|
||||||
|
[]string{"-modify-index", "2", "-recurse"},
|
||||||
|
"Cannot specify both",
|
||||||
|
},
|
||||||
|
"no key": {
|
||||||
|
[]string{},
|
||||||
|
"Missing KEY argument",
|
||||||
|
},
|
||||||
|
"extra args": {
|
||||||
|
[]string{"foo", "bar", "baz"},
|
||||||
|
"Too many arguments",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
// Ensure our buffer is always clear
|
||||||
|
if ui.ErrorWriter != nil {
|
||||||
|
ui.ErrorWriter.Reset()
|
||||||
|
}
|
||||||
|
if ui.OutputWriter != nil {
|
||||||
|
ui.OutputWriter.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Run(tc.args)
|
||||||
|
if code == 0 {
|
||||||
|
t.Errorf("%s: expected non-zero exit", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := ui.ErrorWriter.String()
|
||||||
|
if !strings.Contains(output, tc.output) {
|
||||||
|
t.Errorf("%s: expected %q to contain %q", name, output, tc.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKVDeleteCommand_Run(t *testing.T) {
|
||||||
|
srv, client := testAgentWithAPIClient(t)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
waitForLeader(t, srv.httpAddr)
|
||||||
|
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &KVDeleteCommand{Ui: ui}
|
||||||
|
|
||||||
|
pair := &api.KVPair{
|
||||||
|
Key: "foo",
|
||||||
|
Value: []byte("bar"),
|
||||||
|
}
|
||||||
|
_, err := client.KV().Put(pair, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + srv.httpAddr,
|
||||||
|
"foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Run(args)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
pair, _, err = client.KV().Get("foo", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %#v", err)
|
||||||
|
}
|
||||||
|
if pair != nil {
|
||||||
|
t.Fatalf("bad: %#v", pair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKVDeleteCommand_Recurse(t *testing.T) {
|
||||||
|
srv, client := testAgentWithAPIClient(t)
|
||||||
|
defer srv.Shutdown()
|
||||||
|
waitForLeader(t, srv.httpAddr)
|
||||||
|
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &KVDeleteCommand{Ui: ui}
|
||||||
|
|
||||||
|
keys := []string{"foo/a", "foo/b", "food"}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
pair := &api.KVPair{
|
||||||
|
Key: k,
|
||||||
|
Value: []byte("bar"),
|
||||||
|
}
|
||||||
|
_, err := client.KV().Put(pair, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + srv.httpAddr,
|
||||||
|
"-recurse",
|
||||||
|
"foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Run(args)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
pair, _, err := client.KV().Get(k, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %#v", err)
|
||||||
|
}
|
||||||
|
if pair != nil {
|
||||||
|
t.Fatalf("bad: %#v", pair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Commands: KV Delete"
|
||||||
|
sidebar_current: "docs-commands-kv-delete"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Consul KV Delete
|
||||||
|
|
||||||
|
Command: `consul kv delete`
|
||||||
|
|
||||||
|
The `kv delete` command removes the value from Consul's key-value store at the
|
||||||
|
given path. If no key exists at the path, no action is taken.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `consul kv delete [options] KEY_OR_PREFIX`
|
||||||
|
|
||||||
|
#### API Options
|
||||||
|
|
||||||
|
<%= partial "docs/commands/http_api_options" %>
|
||||||
|
|
||||||
|
#### KV Delete Options
|
||||||
|
|
||||||
|
* `-cas` - Perform a Check-And-Set operation. If this value is specified without
|
||||||
|
-modify-index, the key will first be fetched and the resulting ModifyIndex
|
||||||
|
will be used on the next query. The default value is false.
|
||||||
|
|
||||||
|
* `-modify-index=<int>` - Unsigned integer representing the ModifyIndex of the
|
||||||
|
key. This is often combined with the -cas flag, but it can be specified for
|
||||||
|
any key. The default value is 0.
|
||||||
|
|
||||||
|
* `-recurse` - Recursively delete all keys with the path. The default value is
|
||||||
|
false.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
To remove the value for the key named "redis/config/connections" in the
|
||||||
|
key-value store:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv delete redis/config/connections
|
||||||
|
Success! Deleted key: redis/config/connections
|
||||||
|
```
|
||||||
|
|
||||||
|
If the key does not exist, the command will not error, and a success message
|
||||||
|
will be returned:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv delete not-a-real-key
|
||||||
|
Success! Deleted key: not-a-real-key
|
||||||
|
```
|
||||||
|
|
||||||
|
To only delete a key if it has not been modified since a given index, specify
|
||||||
|
the `-cas` and `-modify-index` flags:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv get -detailed redis/config/connections | grep ModifyIndex
|
||||||
|
ModifyIndex 456
|
||||||
|
|
||||||
|
$ consul kv delete -cas -modify-index=123 redis/config/connections
|
||||||
|
Error! Did not delete key redis/config/connections: CAS failed
|
||||||
|
|
||||||
|
$ consul kv delete -cas -modify-index=456 redis/config/connections
|
||||||
|
Success! Deleted key: redis/config/connections
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to have Consul fetch the current ModifyIndex before making
|
||||||
|
the query, by omitting the `-modify-index` flag. If the data is changed between
|
||||||
|
the initial read and the write, the operation will fail.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv delete -cas redis/config/connections
|
||||||
|
Success! Deleted key: redis/config/connections
|
||||||
|
```
|
||||||
|
|
||||||
|
To recursively delete all keys that start with a given prefix, specify the
|
||||||
|
`-recurse` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv delete -recurse redis/
|
||||||
|
Success! Deleted keys with prefix: redis/
|
||||||
|
```
|
||||||
|
|
||||||
|
!> **Trailing slashes are important** in the recursive delete operation, since
|
||||||
|
Consul performs a greedy match on the provided prefix. If you were to use "foo"
|
||||||
|
as the key, this would recursively delete any key starting with those letters
|
||||||
|
such as "foo", "food", and "football" not just "foo". To ensure you are deleting
|
||||||
|
a folder, always use a trailing slash.
|
||||||
|
|
||||||
|
It is not valid to combine the `-cas` option with `-recurse`, since you are
|
||||||
|
deleting multiple keys under a prefix in a single operation:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ consul kv delete -cas -recurse redis/
|
||||||
|
Cannot specify both -cas and -recurse!
|
||||||
|
```
|
Loading…
Reference in new issue