command/write: new format

This commit is contained in:
Mitchell Hashimoto
2015-03-31 17:16:12 -07:00
parent 657f847790
commit 456c4b0b21
3 changed files with 213 additions and 29 deletions

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
// ReadCommand is a Command that gets data from the Vault. // ReadCommand is a Command that reads data from the Vault.
type ReadCommand struct { type ReadCommand struct {
Meta Meta
} }
@@ -60,7 +60,7 @@ func (c *ReadCommand) Synopsis() string {
func (c *ReadCommand) Help() string { func (c *ReadCommand) Help() string {
helpText := ` helpText := `
Usage: vault get [options] path Usage: vault read [options] path
Read data from Vault. Read data from Vault.

View File

@@ -4,13 +4,11 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"strings" "strings"
) )
// DefaultDataKey is the key used in the write as a default for data.
const DefaultDataKey = "value"
// WriteCommand is a Command that puts data into the Vault. // WriteCommand is a Command that puts data into the Vault.
type WriteCommand struct { type WriteCommand struct {
Meta Meta
@@ -27,8 +25,8 @@ func (c *WriteCommand) Run(args []string) int {
} }
args = flags.Args() args = flags.Args()
if len(args) != 2 { if len(args) < 2 {
c.Ui.Error("write expects two arguments") c.Ui.Error("write expects at least two arguments")
flags.Usage() flags.Usage()
return 1 return 1
} }
@@ -37,22 +35,14 @@ func (c *WriteCommand) Run(args []string) int {
if path[0] == '/' { if path[0] == '/' {
path = path[1:] path = path[1:]
} }
var data map[string]interface{}
if args[1] == "-" {
var stdin io.Reader = os.Stdin
if c.testStdin != nil {
stdin = c.testStdin
}
dec := json.NewDecoder(stdin) data, err := c.parseData(args[1:])
if err := dec.Decode(&data); err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
"Error decoding JSON of stdin: %s", err)) "Error loading data: %s", err))
return 1 return 1
}
} else {
data = map[string]interface{}{DefaultDataKey: args[1]}
} }
println(fmt.Sprintf("%#v", data))
client, err := c.Client() client, err := c.Client()
if err != nil { if err != nil {
@@ -71,13 +61,80 @@ func (c *WriteCommand) Run(args []string) int {
return 0 return 0
} }
func (c *WriteCommand) parseData(args []string) (map[string]interface{}, error) {
result := make(map[string]interface{})
for i, arg := range args {
// If the arg is exactly "-" then we read from stdin and merge
// the resulting structure into the result.
if arg == "-" {
var stdin io.Reader = os.Stdin
if c.testStdin != nil {
stdin = c.testStdin
}
dec := json.NewDecoder(stdin)
if err := dec.Decode(&result); err != nil {
return nil, fmt.Errorf(
"Error loading data at index %d: %s", i, err)
}
continue
}
// If the arg begins with "@" then we read the file directly.
if arg[0] == '@' {
f, err := os.Open(arg[1:])
if err != nil {
return nil, fmt.Errorf(
"Error loading data at index %d: %s", i, err)
}
dec := json.NewDecoder(f)
err = dec.Decode(&result)
f.Close()
if err != nil {
return nil, fmt.Errorf(
"Error loading data at index %d: %s", i, err)
}
continue
}
// Split into key/value
parts := strings.SplitN(arg, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf(
"Data at index %d is not in key=value format: %s",
i, arg)
}
key, value := parts[0], parts[1]
if value[0] == '@' {
contents, err := ioutil.ReadFile(value[1:])
if err != nil {
return nil, fmt.Errorf(
"Error reading file value for index %d: %s", i, err)
}
value = string(contents)
} else if value[0] == '\\' && value[1] == '@' {
value = value[1:]
}
result[key] = value
}
return result, nil
}
func (c *WriteCommand) Synopsis() string { func (c *WriteCommand) Synopsis() string {
return "Write secrets or configuration into Vault" return "Write secrets or configuration into Vault"
} }
func (c *WriteCommand) Help() string { func (c *WriteCommand) Help() string {
helpText := ` helpText := `
Usage: vault write [options] path data Usage: vault write [options] path [data]
Write data (secrets or configuration) into Vault. Write data (secrets or configuration) into Vault.
@@ -88,11 +145,9 @@ Usage: vault write [options] path data
into Consul at that key. Check the documentation of the logical backend into Consul at that key. Check the documentation of the logical backend
you're using for more information on key structure. you're using for more information on key structure.
If data is "-" then the data will be ready from stdin. To write a literal Data is sent via additional arguments in "key=value" pairs. If value
"-", you'll have to pipe that value in from stdin. To write data from a begins with an "@", then it is loaded from a file. If you want to start
file, pipe the file contents in via stdin and set data to "-". the value with a literal "@", then prefix the "@" with a slash: "\@".
If data is a string, it will be sent with the key of "value".
General Options: General Options:

View File

@@ -2,6 +2,8 @@ package command
import ( import (
"io" "io"
"io/ioutil"
"os"
"testing" "testing"
"github.com/hashicorp/vault/http" "github.com/hashicorp/vault/http"
@@ -25,7 +27,7 @@ func TestWrite(t *testing.T) {
args := []string{ args := []string{
"-address", addr, "-address", addr,
"secret/foo", "secret/foo",
"bar", "value=bar",
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
@@ -41,7 +43,7 @@ func TestWrite(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if resp.Data[DefaultDataKey] != "bar" { if resp.Data["value"] != "bar" {
t.Fatalf("bad: %#v", resp) t.Fatalf("bad: %#v", resp)
} }
} }
@@ -90,3 +92,130 @@ func TestWrite_arbitrary(t *testing.T) {
t.Fatalf("bad: %#v", resp) t.Fatalf("bad: %#v", resp)
} }
} }
func TestWrite_escaped(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi)
c := &WriteCommand{
Meta: Meta{
ClientToken: token,
Ui: ui,
},
}
args := []string{
"-address", addr,
"secret/foo",
"value=\\@bar",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
resp, err := client.Logical().Read("secret/foo")
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data["value"] != "@bar" {
t.Fatalf("bad: %#v", resp)
}
}
func TestWrite_file(t *testing.T) {
tf, err := ioutil.TempFile("", "vault")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Write([]byte(`{"foo":"bar"}`))
tf.Close()
defer os.Remove(tf.Name())
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi)
c := &WriteCommand{
Meta: Meta{
ClientToken: token,
Ui: ui,
},
}
args := []string{
"-address", addr,
"secret/foo",
"@" + tf.Name(),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
resp, err := client.Logical().Read("secret/foo")
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data["foo"] != "bar" {
t.Fatalf("bad: %#v", resp)
}
}
func TestWrite_fileValue(t *testing.T) {
tf, err := ioutil.TempFile("", "vault")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Write([]byte("foo"))
tf.Close()
defer os.Remove(tf.Name())
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi)
c := &WriteCommand{
Meta: Meta{
ClientToken: token,
Ui: ui,
},
}
args := []string{
"-address", addr,
"secret/foo",
"value=@" + tf.Name(),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
resp, err := client.Logical().Read("secret/foo")
if err != nil {
t.Fatalf("err: %s", err)
}
if resp.Data["value"] != "foo" {
t.Fatalf("bad: %#v", resp)
}
}