Add list capability, which will work with the generic and cubbyhole

backends for the moment. This is pretty simple; it just adds the actual
capability to make a list call into both the CLI and the HTTP handler.
The real meat was already in those backends.
This commit is contained in:
Jeff Mitchell
2015-09-14 15:42:12 -04:00
parent fdc7e717ee
commit 4b67fd139f
4 changed files with 120 additions and 4 deletions

View File

@@ -26,6 +26,20 @@ func (c *Logical) Read(path string) (*Secret, error) {
return ParseSecret(resp.Body)
}
func (c *Logical) List(path string) (*Secret, error) {
r := c.c.NewRequest("LIST", "/v1/"+path)
resp, err := c.c.RawRequest(r)
if resp != nil && resp.StatusCode == 404 {
return nil, nil
}
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ParseSecret(resp.Body)
}
func (c *Logical) Write(path string, data map[string]interface{}) (*Secret, error) {
r := c.c.NewRequest("PUT", "/v1/"+path)
if err := r.SetJSONBody(data); err != nil {

View File

@@ -161,6 +161,7 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
"read": func() (cli.Command, error) {
return &command.ReadCommand{
Meta: meta,
List: false,
}, nil
},
@@ -176,6 +177,13 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
}, nil
},
"list": func() (cli.Command, error) {
return &command.ReadCommand{
Meta: meta,
List: true,
}, nil
},
"rekey": func() (cli.Command, error) {
return &command.RekeyCommand{
Meta: meta,

View File

@@ -1,21 +1,32 @@
package command
import (
"flag"
"fmt"
"os"
"reflect"
"strings"
"github.com/hashicorp/vault/api"
)
// ReadCommand is a Command that reads data from the Vault.
type ReadCommand struct {
Meta
List bool
}
func (c *ReadCommand) Run(args []string) int {
var format string
var field string
flags := c.Meta.FlagSet("read", FlagSetDefault)
var err error
var secret *api.Secret
var flags *flag.FlagSet
if c.List {
flags = c.Meta.FlagSet("list", FlagSetDefault)
} else {
flags = c.Meta.FlagSet("read", FlagSetDefault)
}
flags.StringVar(&format, "format", "table", "")
flags.StringVar(&field, "field", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
@@ -42,7 +53,11 @@ func (c *ReadCommand) Run(args []string) int {
return 2
}
secret, err := client.Logical().Read(path)
if c.List {
secret, err = client.Logical().List(path)
} else {
secret, err = client.Logical().Read(path)
}
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error reading %s: %s", path, err))
@@ -79,6 +94,9 @@ func (c *ReadCommand) Run(args []string) int {
}
func (c *ReadCommand) Synopsis() string {
if c.List {
return "List data in Vault"
}
return "Read data or secrets from Vault"
}
@@ -88,11 +106,25 @@ Usage: vault read [options] path
Read data from Vault.
Read reads data at the given path from Vault. This can be used to
read secrets and configuration as well as generate dynamic values from
Reads data at the given path from Vault. This can be used to read
secrets and configuration as well as generate dynamic values from
materialized backends. Please reference the documentation for the
backends in use to determine key structure.
`
if c.List {
helpText =
`
Usage: vault list [options] path
List data from Vault.
Retrieve a listing of available data. The data returned is
backend-specific, and not all backends implement listing capability.
`
}
helpText += `
General Options:
` + generalOptionsUsage() + `

View File

@@ -1,6 +1,7 @@
package command
import (
"reflect"
"testing"
"github.com/hashicorp/vault/http"
@@ -134,3 +135,64 @@ func TestRead_field_notFound(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestList(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
ui := new(cli.MockUi)
c := &ReadCommand{
Meta: Meta{
ClientToken: token,
Ui: ui,
},
List: true,
}
args := []string{
"-address", addr,
"-format", "json",
"secret",
}
// Run once so the client is setup, ignore errors
c.Run(args)
// Get the client so we can write data
client, err := c.Client()
if err != nil {
t.Fatalf("err: %s", err)
}
data := map[string]interface{}{"value": "bar"}
if _, err := client.Logical().Write("secret/foo", data); err != nil {
t.Fatalf("err: %s", err)
}
data = map[string]interface{}{"value": "bar"}
if _, err := client.Logical().Write("secret/foo/bar", data); err != nil {
t.Fatalf("err: %s", err)
}
secret, err := client.Logical().List("secret/")
if err != nil {
t.Fatalf("err: %s", err)
}
if secret == nil {
t.Fatalf("err: No value found at secret/")
}
if secret.Data == nil {
t.Fatalf("err: Data not found")
}
exp := map[string]interface{}{
"keys": []interface{}{"foo", "foo/"},
}
if !reflect.DeepEqual(secret.Data, exp) {
t.Fatalf("err: expected %#v, got %#v", exp, secret.Data)
}
}