mirror of https://github.com/status-im/consul.git
Merge branch 'master' into master
This commit is contained in:
commit
78a53e18a7
|
@ -1,7 +1,14 @@
|
|||
## 0.7.3 (UNRELEASED)
|
||||
|
||||
FEATURES:
|
||||
|
||||
* **KV Import/Export CLI:** `consul kv export` and `consul kv import` can be used to move parts of the KV tree between disconnected consul clusters, using JSON as the intermediate representation. [GH-#2633]
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
* `consul kv get` now has a `-base64` flag to base 64 encode the value. [GH-2631]
|
||||
* `consul kv put` now has a `-base64` flag for setting values which are base 64 encoded. [GH-2632]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
## 0.7.2 (December 19, 2016)
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// KVExportCommand is a Command implementation that is used to export
|
||||
// a KV tree as JSON
|
||||
type KVExportCommand struct {
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *KVExportCommand) Synopsis() string {
|
||||
return "Exports a tree from the KV store as JSON"
|
||||
}
|
||||
|
||||
func (c *KVExportCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: consul kv export [KEY_OR_PREFIX]
|
||||
|
||||
Retrieves key-value pairs for the given prefix from Consul's key-value store,
|
||||
and writes a JSON representation to stdout. This can be used with the command
|
||||
"consul kv import" to move entire trees between Consul clusters.
|
||||
|
||||
$ consul kv export vault
|
||||
|
||||
For a full list of options and examples, please see the Consul documentation.
|
||||
|
||||
` + apiOptsText + `
|
||||
|
||||
KV Export Options:
|
||||
|
||||
None.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVExportCommand) Run(args []string) int {
|
||||
cmdFlags := flag.NewFlagSet("export", flag.ContinueOnError)
|
||||
|
||||
datacenter := cmdFlags.String("datacenter", "", "")
|
||||
token := cmdFlags.String("token", "", "")
|
||||
stale := cmdFlags.Bool("stale", 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:]
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
pairs, _, err := client.KV().List(key, &api.QueryOptions{
|
||||
Datacenter: *datacenter,
|
||||
AllowStale: *stale,
|
||||
})
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
exported := make([]*kvExportEntry, len(pairs))
|
||||
for i, pair := range pairs {
|
||||
exported[i] = toExportEntry(pair)
|
||||
}
|
||||
|
||||
marshaled, err := json.MarshalIndent(exported, "", "\t")
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error exporting KV data: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Info(string(marshaled))
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
type kvExportEntry struct {
|
||||
Key string `json:"key"`
|
||||
Flags uint64 `json:"flags"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func toExportEntry(pair *api.KVPair) *kvExportEntry {
|
||||
return &kvExportEntry{
|
||||
Key: pair.Key,
|
||||
Flags: pair.Flags,
|
||||
Value: base64.StdEncoding.EncodeToString(pair.Value),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestKVExportCommand_Run(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVExportCommand{Ui: ui}
|
||||
|
||||
keys := map[string]string{
|
||||
"foo/a": "a",
|
||||
"foo/b": "b",
|
||||
"foo/c": "c",
|
||||
"bar": "d",
|
||||
}
|
||||
for k, v := range keys {
|
||||
pair := &api.KVPair{Key: k, Value: []byte(v)}
|
||||
if _, err := client.KV().Put(pair, nil); 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())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
|
||||
var exported []*kvExportEntry
|
||||
err := json.Unmarshal([]byte(output), &exported)
|
||||
if err != nil {
|
||||
t.Fatalf("bad: %d", code)
|
||||
}
|
||||
|
||||
if len(exported) != 3 {
|
||||
t.Fatalf("bad: expected 3, got %d", len(exported))
|
||||
}
|
||||
|
||||
for _, entry := range exported {
|
||||
if base64.StdEncoding.EncodeToString([]byte(keys[entry.Key])) != entry.Value {
|
||||
t.Fatalf("bad: expected %s, got %s", keys[entry.Key], entry.Value)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -54,6 +55,8 @@ Usage: consul kv get [options] [KEY_OR_PREFIX]
|
|||
|
||||
KV Get Options:
|
||||
|
||||
-base64 Base64 encode the value. The default value is false.
|
||||
|
||||
-detailed Provide additional metadata about the key in addition
|
||||
to the value such as the ModifyIndex and any flags
|
||||
that may have been set on the key. The default value
|
||||
|
@ -84,6 +87,7 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
stale := cmdFlags.Bool("stale", false, "")
|
||||
detailed := cmdFlags.Bool("detailed", false, "")
|
||||
keys := cmdFlags.Bool("keys", false, "")
|
||||
base64encode := cmdFlags.Bool("base64", false, "")
|
||||
recurse := cmdFlags.Bool("recurse", false, "")
|
||||
separator := cmdFlags.String("separator", "/", "")
|
||||
httpAddr := HTTPAddrFlag(cmdFlags)
|
||||
|
@ -158,7 +162,7 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
for i, pair := range pairs {
|
||||
if *detailed {
|
||||
var b bytes.Buffer
|
||||
if err := prettyKVPair(&b, pair); err != nil {
|
||||
if err := prettyKVPair(&b, pair, *base64encode); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error rendering KV pair: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
@ -169,7 +173,11 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
c.Ui.Info("")
|
||||
}
|
||||
} else {
|
||||
c.Ui.Info(fmt.Sprintf("%s:%s", pair.Key, pair.Value))
|
||||
if *base64encode {
|
||||
c.Ui.Info(fmt.Sprintf("%s:%s", pair.Key, base64.StdEncoding.EncodeToString(pair.Value)))
|
||||
} else {
|
||||
c.Ui.Info(fmt.Sprintf("%s:%s", pair.Key, pair.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +199,7 @@ func (c *KVGetCommand) Run(args []string) int {
|
|||
|
||||
if *detailed {
|
||||
var b bytes.Buffer
|
||||
if err := prettyKVPair(&b, pair); err != nil {
|
||||
if err := prettyKVPair(&b, pair, *base64encode); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error rendering KV pair: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
@ -209,7 +217,7 @@ func (c *KVGetCommand) Synopsis() string {
|
|||
return "Retrieves or lists data from the KV store"
|
||||
}
|
||||
|
||||
func prettyKVPair(w io.Writer, pair *api.KVPair) error {
|
||||
func prettyKVPair(w io.Writer, pair *api.KVPair, base64EncodeValue bool) error {
|
||||
tw := tabwriter.NewWriter(w, 0, 2, 6, ' ', 0)
|
||||
fmt.Fprintf(tw, "CreateIndex\t%d\n", pair.CreateIndex)
|
||||
fmt.Fprintf(tw, "Flags\t%d\n", pair.Flags)
|
||||
|
@ -217,10 +225,14 @@ func prettyKVPair(w io.Writer, pair *api.KVPair) error {
|
|||
fmt.Fprintf(tw, "LockIndex\t%d\n", pair.LockIndex)
|
||||
fmt.Fprintf(tw, "ModifyIndex\t%d\n", pair.ModifyIndex)
|
||||
if pair.Session == "" {
|
||||
fmt.Fprintf(tw, "Session\t-\n")
|
||||
fmt.Fprint(tw, "Session\t-\n")
|
||||
} else {
|
||||
fmt.Fprintf(tw, "Session\t%s\n", pair.Session)
|
||||
}
|
||||
fmt.Fprintf(tw, "Value\t%s", pair.Value)
|
||||
if base64EncodeValue {
|
||||
fmt.Fprintf(tw, "Value\t%s", base64.StdEncoding.EncodeToString(pair.Value))
|
||||
} else {
|
||||
fmt.Fprintf(tw, "Value\t%s", pair.Value)
|
||||
}
|
||||
return tw.Flush()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -250,3 +251,91 @@ func TestKVGetCommand_Recurse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVGetCommand_RecurseBase64(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVGetCommand{Ui: ui}
|
||||
|
||||
keys := map[string]string{
|
||||
"foo/a": "Hello World 1",
|
||||
"foo/b": "Hello World 2",
|
||||
"foo/c": "Hello World 3",
|
||||
}
|
||||
for k, v := range keys {
|
||||
pair := &api.KVPair{Key: k, Value: []byte(v)}
|
||||
if _, err := client.KV().Put(pair, nil); err != nil {
|
||||
t.Fatalf("err: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + srv.httpAddr,
|
||||
"-recurse",
|
||||
"-base64",
|
||||
"foo",
|
||||
}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
for key, value := range keys {
|
||||
if !strings.Contains(output, key+":"+base64.StdEncoding.EncodeToString([]byte(value))) {
|
||||
t.Fatalf("bad %#v missing %q", output, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVGetCommand_DetailedBase64(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVGetCommand{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,
|
||||
"-detailed",
|
||||
"-base64",
|
||||
"foo",
|
||||
}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
for _, key := range []string{
|
||||
"CreateIndex",
|
||||
"LockIndex",
|
||||
"ModifyIndex",
|
||||
"Flags",
|
||||
"Session",
|
||||
"Value",
|
||||
} {
|
||||
if !strings.Contains(output, key) {
|
||||
t.Fatalf("bad %#v, missing %q", output, key)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(output, base64.StdEncoding.EncodeToString([]byte("bar"))) {
|
||||
t.Fatalf("bad %#v, value is not base64 encoded", output)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// KVImportCommand is a Command implementation that is used to import
|
||||
// a KV tree stored as JSON
|
||||
type KVImportCommand struct {
|
||||
Ui cli.Ui
|
||||
|
||||
// testStdin is the input for testing.
|
||||
testStdin io.Reader
|
||||
}
|
||||
|
||||
func (c *KVImportCommand) Synopsis() string {
|
||||
return "Imports a tree stored as JSON to the KV store"
|
||||
}
|
||||
|
||||
func (c *KVImportCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: consul kv import [DATA]
|
||||
|
||||
Imports key-value pairs to the key-value store from the JSON representation
|
||||
generated by the "consul kv export" command.
|
||||
|
||||
The data can be read from a file by prefixing the filename with the "@"
|
||||
symbol. For example:
|
||||
|
||||
$ consul kv import @filename.json
|
||||
|
||||
Or it can be read from stdin using the "-" symbol:
|
||||
|
||||
$ cat filename.json | consul kv import config/program/license -
|
||||
|
||||
Alternatively the data may be provided as the final parameter to the command,
|
||||
though care must be taken with regards to shell escaping.
|
||||
|
||||
For a full list of options and examples, please see the Consul documentation.
|
||||
|
||||
` + apiOptsText + `
|
||||
|
||||
KV Import Options:
|
||||
|
||||
None.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *KVImportCommand) Run(args []string) int {
|
||||
cmdFlags := flag.NewFlagSet("import", flag.ContinueOnError)
|
||||
|
||||
datacenter := cmdFlags.String("datacenter", "", "")
|
||||
token := cmdFlags.String("token", "", "")
|
||||
httpAddr := HTTPAddrFlag(cmdFlags)
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Check for arg validation
|
||||
args = cmdFlags.Args()
|
||||
data, err := c.dataFromArgs(args)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error! %s", err))
|
||||
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
|
||||
}
|
||||
|
||||
var entries []*kvExportEntry
|
||||
if err := json.Unmarshal([]byte(data), &entries); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Cannot unmarshal data: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
value, err := base64.StdEncoding.DecodeString(entry.Value)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error base 64 decoding value for key %s: %s", entry.Key, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
pair := &api.KVPair{
|
||||
Key: entry.Key,
|
||||
Flags: entry.Flags,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
wo := &api.WriteOptions{
|
||||
Datacenter: *datacenter,
|
||||
Token: *token,
|
||||
}
|
||||
|
||||
if _, err := client.KV().Put(pair, wo); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error! Failed writing data for key %s: %s", pair.Key, err))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Info(fmt.Sprintf("Imported: %s", pair.Key))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *KVImportCommand) dataFromArgs(args []string) (string, error) {
|
||||
var stdin io.Reader = os.Stdin
|
||||
if c.testStdin != nil {
|
||||
stdin = c.testStdin
|
||||
}
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return "", errors.New("Missing DATA argument")
|
||||
case 1:
|
||||
default:
|
||||
return "", fmt.Errorf("Too many arguments (expected 1 or 2, got %d)", len(args))
|
||||
}
|
||||
|
||||
data := args[0]
|
||||
|
||||
if len(data) == 0 {
|
||||
return "", errors.New("Empty DATA argument")
|
||||
}
|
||||
|
||||
switch data[0] {
|
||||
case '@':
|
||||
data, err := ioutil.ReadFile(data[1:])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to read file: %s", err)
|
||||
}
|
||||
return string(data), nil
|
||||
case '-':
|
||||
if len(data) > 1 {
|
||||
return data, nil
|
||||
} else {
|
||||
var b bytes.Buffer
|
||||
if _, err := io.Copy(&b, stdin); err != nil {
|
||||
return "", fmt.Errorf("Failed to read stdin: %s", err)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
default:
|
||||
return data, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestKVImportCommand_Run(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
const json = `[
|
||||
{
|
||||
"key": "foo",
|
||||
"flags": 0,
|
||||
"value": "YmFyCg=="
|
||||
},
|
||||
{
|
||||
"key": "foo/a",
|
||||
"flags": 0,
|
||||
"value": "YmF6Cg=="
|
||||
}
|
||||
]`
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVImportCommand{
|
||||
Ui: ui,
|
||||
testStdin: strings.NewReader(json),
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + srv.httpAddr,
|
||||
"-",
|
||||
}
|
||||
|
||||
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.Fatal(err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(pair.Value)) != "bar" {
|
||||
t.Fatalf("bad: expected: bar, got %s", pair.Value)
|
||||
}
|
||||
|
||||
pair, _, err = client.KV().Get("foo/a", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(pair.Value)) != "baz" {
|
||||
t.Fatalf("bad: expected: baz, got %s", pair.Value)
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -45,6 +46,9 @@ Usage: consul kv put [options] KEY [DATA]
|
|||
|
||||
$ consul kv put webapp/beta/active
|
||||
|
||||
If the -base64 flag is specified, the data will be treated as base 64
|
||||
encoded.
|
||||
|
||||
To perform a Check-And-Set operation, specify the -cas flag with the
|
||||
appropriate -modify-index flag corresponding to the key you want to perform
|
||||
the CAS operation on:
|
||||
|
@ -62,6 +66,9 @@ KV Put Options:
|
|||
lock. The session must already exist and be specified
|
||||
via the -session flag. The default value is false.
|
||||
|
||||
-base64 Treat the data as base 64 encoded. The default value
|
||||
is false.
|
||||
|
||||
-cas Perform a Check-And-Set operation. Specifying this
|
||||
value also requires the -modify-index flag to be set.
|
||||
The default value is false.
|
||||
|
@ -95,6 +102,7 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
token := cmdFlags.String("token", "", "")
|
||||
cas := cmdFlags.Bool("cas", false, "")
|
||||
flags := cmdFlags.Uint64("flags", 0, "")
|
||||
base64encoded := cmdFlags.Bool("base64", false, "")
|
||||
modifyIndex := cmdFlags.Uint64("modify-index", 0, "")
|
||||
session := cmdFlags.String("session", "", "")
|
||||
acquire := cmdFlags.Bool("acquire", false, "")
|
||||
|
@ -111,6 +119,14 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
dataBytes := []byte(data)
|
||||
if *base64encoded {
|
||||
dataBytes, err = base64.StdEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error! Cannot base 64 decode data: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Session is reauired for release or acquire
|
||||
if (*release || *acquire) && *session == "" {
|
||||
c.Ui.Error("Error! Missing -session (required with -acquire and -release)")
|
||||
|
@ -137,7 +153,7 @@ func (c *KVPutCommand) Run(args []string) int {
|
|||
Key: key,
|
||||
ModifyIndex: *modifyIndex,
|
||||
Flags: *flags,
|
||||
Value: []byte(data),
|
||||
Value: dataBytes,
|
||||
Session: *session,
|
||||
}
|
||||
|
||||
|
@ -220,6 +236,11 @@ func (c *KVPutCommand) dataFromArgs(args []string) (string, string, error) {
|
|||
key := args[0]
|
||||
data := args[1]
|
||||
|
||||
// Handle empty quoted shell parameters
|
||||
if len(data) == 0 {
|
||||
return key, "", nil
|
||||
}
|
||||
|
||||
switch data[0] {
|
||||
case '@':
|
||||
data, err := ioutil.ReadFile(data[1:])
|
||||
|
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -100,6 +101,70 @@ func TestKVPutCommand_Run(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestKVPutCommand_RunEmptyDataQuoted(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVPutCommand{Ui: ui}
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + srv.httpAddr,
|
||||
"foo", "",
|
||||
}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
data, _, err := client.KV().Get("foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if data.Value != nil {
|
||||
t.Errorf("bad: %#v", data.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVPutCommand_RunBase64(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
waitForLeader(t, srv.httpAddr)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &KVPutCommand{Ui: ui}
|
||||
|
||||
const encodedString = "aGVsbG8gd29ybGQK"
|
||||
|
||||
args := []string{
|
||||
"-http-addr=" + srv.httpAddr,
|
||||
"-base64",
|
||||
"foo", encodedString,
|
||||
}
|
||||
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
data, _, err := client.KV().Get("foo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected, err := base64.StdEncoding.DecodeString(encodedString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data.Value, []byte(expected)) {
|
||||
t.Errorf("bad: %#v, %s", data.Value, data.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVPutCommand_File(t *testing.T) {
|
||||
srv, client := testAgentWithAPIClient(t)
|
||||
defer srv.Shutdown()
|
||||
|
|
12
commands.go
12
commands.go
|
@ -78,6 +78,18 @@ func init() {
|
|||
}, nil
|
||||
},
|
||||
|
||||
"kv export": func() (cli.Command, error) {
|
||||
return &command.KVExportCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"kv import": func() (cli.Command, error) {
|
||||
return &command.KVImportCommand{
|
||||
Ui: ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"join": func() (cli.Command, error) {
|
||||
return &command.JoinCommand{
|
||||
Ui: ui,
|
||||
|
|
|
@ -24,6 +24,7 @@ variable "ami" {
|
|||
eu-west-1-ubuntu = "ami-47a23a30"
|
||||
eu-central-1-ubuntu = "ami-accff2b1"
|
||||
ap-northeast-1-ubuntu = "ami-90815290"
|
||||
ap-northeast-2-ubuntu = "ami-58af6136"
|
||||
ap-southeast-1-ubuntu = "ami-0accf458"
|
||||
ap-southeast-2-ubuntu = "ami-1dc8b127"
|
||||
us-east-1-rhel6 = "ami-0d28fe66"
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<noscript>
|
||||
<center>
|
||||
<h2>JavaScript Required</h2>
|
||||
<p>Please enable JavaScript in your web browser to use Consul UI.</p>
|
||||
</center>
|
||||
</noscript>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="col-md-12">
|
||||
|
|
|
@ -477,7 +477,8 @@ information.
|
|||
|
||||
If `Check` is provided, only one of `Script`, `HTTP`, `TCP` or `TTL`
|
||||
should be specified. `Script` and `HTTP` also require `Interval`. The
|
||||
created check will be named `service:<ServiceId>`.
|
||||
created check will be named `service:<ServiceId>`. The `Status` field
|
||||
can be provided to specify the initial state of the health check.
|
||||
|
||||
In Consul 0.7 and later, checks that are associated with a service may
|
||||
also contain an optional `DeregisterCriticalServiceAfter` field, which
|
||||
|
|
|
@ -31,7 +31,9 @@ Usage: consul kv <subcommand> [options] [args]
|
|||
Subcommands:
|
||||
|
||||
delete Removes data from the KV store
|
||||
export Exports part of the KV tree in JSON format
|
||||
get Retrieves or lists data from the KV store
|
||||
import Imports part of the KV tree in JSON format
|
||||
put Sets or updates data in the KV store
|
||||
```
|
||||
|
||||
|
@ -39,7 +41,9 @@ For more information, examples, and usage about a subcommand, click on the name
|
|||
of the subcommand in the sidebar or one of the links below:
|
||||
|
||||
- [delete](/docs/commands/kv/delete.html)
|
||||
- [export](/docs/commands/kv/export.html)
|
||||
- [get](/docs/commands/kv/get.html)
|
||||
- [import](/docs/commands/kv/import.html)
|
||||
- [put](/docs/commands/kv/put.html)
|
||||
|
||||
## Basic Examples
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Commands: KV Export"
|
||||
sidebar_current: "docs-commands-kv-export"
|
||||
---
|
||||
|
||||
# Consul KV Export
|
||||
|
||||
Command: `consul kv export`
|
||||
|
||||
The `kv export` command is used to retrieve key-value pairs for the given
|
||||
prefix from Consul's key-value store, and write a JSON representation to
|
||||
stdout. This can be used with the command "consul kv import" to move entire
|
||||
trees between Consul clusters.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `consul kv export [PREFIX]`
|
||||
|
||||
#### API Options
|
||||
|
||||
<%= partial "docs/commands/http_api_options" %>
|
||||
|
||||
## Examples
|
||||
|
||||
To export the tree at "vault/" in the key value store:
|
||||
|
||||
```
|
||||
$ consul kv export vault/
|
||||
# JSON output
|
||||
```
|
|
@ -24,6 +24,8 @@ Usage: `consul kv get [options] [KEY_OR_PREFIX]`
|
|||
|
||||
#### KV Get Options
|
||||
|
||||
* `-base64` - Base 64 encode the value. The default value is false.
|
||||
|
||||
* `-detailed` - Provide additional metadata about the key in addition to the
|
||||
value such as the ModifyIndex and any flags that may have been set on the key.
|
||||
The default value is false.
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Commands: KV Import"
|
||||
sidebar_current: "docs-commands-kv-import"
|
||||
---
|
||||
|
||||
# Consul KV Import
|
||||
|
||||
Command: `consul kv import`
|
||||
|
||||
The `kv import` command is used to import KV pairs from the JSON representation
|
||||
generated by the `kv export` command.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `consul kv import [DATA]`
|
||||
|
||||
#### API Options
|
||||
|
||||
<%= partial "docs/commands/http_api_options" %>
|
||||
|
||||
## Examples
|
||||
|
||||
To import from a file, prepend the filename with `@`:
|
||||
|
||||
```
|
||||
$ consul kv import @values.json
|
||||
# Output
|
||||
```
|
||||
|
||||
To import from stdin, use `-` as the data parameter:
|
||||
|
||||
```
|
||||
$ cat values.json | consul kv import -
|
||||
# Output
|
||||
```
|
||||
|
||||
You can also pass the JSON directly, however care must be taken with shell
|
||||
escaping:
|
||||
|
||||
```
|
||||
$ consul kv import "$(cat values.json)"
|
||||
# Output
|
||||
```
|
||||
|
||||
|
|
@ -24,6 +24,8 @@ Usage: `consul kv put [options] KEY [DATA]`
|
|||
operation will create the key and obtain the lock. The session must already
|
||||
exist and be specified via the -session flag. The default value is false.
|
||||
|
||||
* `-base64` - Treat the data as base 64 encoded. The default value is false.
|
||||
|
||||
* `-cas` - Perform a Check-And-Set operation. Specifying this value also
|
||||
requires the -modify-index flag to be set. The default value is false.
|
||||
|
||||
|
@ -60,6 +62,13 @@ $ consul kv put redis/config/connections
|
|||
Success! Data written to: redis/config/connections
|
||||
```
|
||||
|
||||
If the `-base64` flag is set, the data will be decoded before writing:
|
||||
|
||||
```
|
||||
$ consul kv put -base64 foo/encoded aGVsbG8gd29ybGQK
|
||||
Success! Data written to: foo/encoded
|
||||
```
|
||||
|
||||
!> **Be careful when overwriting data!** The above operation would overwrite
|
||||
the value at the key to the empty value.
|
||||
|
||||
|
|
|
@ -102,6 +102,9 @@ description: |-
|
|||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/jippi/hashi-ui">hashi-ui</a> - A modern user interface for the Consul and Nomad
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.cfg4j.org">cfg4j</a> - Configuration library for Java distributed apps. Reads and auto-updates configuration from Consul KVs (and others)
|
||||
</li>
|
||||
|
@ -111,6 +114,9 @@ description: |-
|
|||
<li>
|
||||
<a href="https://github.com/kelseyhightower/confd">confd</a> - Manage local application configuration files using templates and data from etcd or Consul
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/ncbi/consul-announcer">consul-announcer</a> - Command line wrapper for registering services in Consul
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/myENA/consul-backinator">consul-backinator</a> - Command line Consul KV backup and restoration utility
|
||||
</li>
|
||||
|
|
|
@ -109,7 +109,7 @@ $ consul kv get foo
|
|||
zip
|
||||
```
|
||||
|
||||
Consul can provide atomic key updates using a Check-And_set operation. To perform a CAS operation, specify the `-cas` flag:
|
||||
Consul can provide atomic key updates using a Check-And-Set operation. To perform a CAS operation, specify the `-cas` flag:
|
||||
|
||||
```sh
|
||||
$ consul kv put -cas -modify-index=123 foo bar
|
||||
|
|
Loading…
Reference in New Issue