Update read command

This commit is contained in:
Seth Vargo 2017-09-05 00:03:36 -04:00
parent 0d598a7f1e
commit ad1482e123
No known key found for this signature in database
GPG Key ID: C921994F9C27E0FF
2 changed files with 202 additions and 197 deletions

View File

@ -1,109 +1,96 @@
package command
import (
"flag"
"fmt"
"strings"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/meta"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
// ReadCommand is a Command that reads data from the Vault.
// Ensure we are implementing the right interfaces.
var _ cli.Command = (*ReadCommand)(nil)
var _ cli.CommandAutocomplete = (*ReadCommand)(nil)
// ReadCommand is a command that reads data from the Vault.
type ReadCommand struct {
meta.Meta
}
func (c *ReadCommand) Run(args []string) int {
var format string
var field string
var err error
var secret *api.Secret
var flags *flag.FlagSet
flags = c.Meta.FlagSet("read", meta.FlagSetDefault)
flags.StringVar(&format, "format", "table", "")
flags.StringVar(&field, "field", "", "")
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
args = flags.Args()
if len(args) != 1 || len(args[0]) == 0 {
c.Ui.Error("read expects one argument")
flags.Usage()
return 1
}
path := args[0]
if path[0] == '/' {
path = path[1:]
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing client: %s", err))
return 2
}
secret, err = client.Logical().Read(path)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error reading %s: %s", path, err))
return 1
}
if secret == nil {
c.Ui.Error(fmt.Sprintf(
"No value found at %s", path))
return 1
}
// Handle single field output
if field != "" {
return PrintRawField(c.Ui, secret, field)
}
return OutputSecret(c.Ui, format, secret)
*BaseCommand
}
func (c *ReadCommand) Synopsis() string {
return "Read data or secrets from Vault"
return "Reads data and retrieves secrets"
}
func (c *ReadCommand) Help() string {
helpText := `
Usage: vault read [options] path
Usage: vault read [options] PATH
Read data from Vault.
Reads data from Vault at the given path. This can be used to read secrets,
generate dynamic credentials, get configuration details, and more.
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.
Read a secret from the static secret backend:
General Options:
` + meta.GeneralOptionsUsage() + `
Read Options:
$ vault read secret/my-secret
-format=table The format for output. By default it is a whitespace-
delimited table. This can also be json or yaml.
For a full list of examples and paths, please see the documentation that
corresponds to the secret backend in use.
-field=field If included, the raw value of the specified field
will be output raw to stdout.
` + c.Flags().Help()
`
return strings.TrimSpace(helpText)
}
func (c *ReadCommand) Flags() *FlagSets {
return c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
}
func (c *ReadCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
return c.PredictVaultFiles()
}
func (c *ReadCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-format": predictFormat,
"-field": complete.PredictNothing,
}
return c.Flags().Completions()
}
func (c *ReadCommand) Run(args []string) int {
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
path, kvs, err := extractPath(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if len(kvs) > 0 {
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(err.Error())
return 2
}
secret, err := client.Logical().Read(path)
if err != nil {
c.UI.Error(fmt.Sprintf("Error reading %s: %s", path, err))
return 2
}
if secret == nil {
c.UI.Error(fmt.Sprintf("No value found at %s", path))
return 2
}
if c.flagField != "" {
return PrintRawField(c.UI, secret, c.flagField)
}
return OutputSecret(c.UI, c.flagFormat, secret)
}

View File

@ -1,137 +1,155 @@
package command
import (
"strings"
"testing"
"github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/meta"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/cli"
)
func TestRead(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func testReadCommand(tb testing.TB) (*cli.MockUi, *ReadCommand) {
tb.Helper()
ui := new(cli.MockUi)
c := &ReadCommand{
Meta: meta.Meta{
ClientToken: token,
Ui: ui,
ui := cli.NewMockUi()
return ui, &ReadCommand{
BaseCommand: &BaseCommand{
UI: ui,
},
}
args := []string{
"-address", addr,
"sys/mounts",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestRead_notFound(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
ln, addr := http.TestServer(t, core)
defer ln.Close()
func TestReadCommand_Run(t *testing.T) {
t.Parallel()
ui := new(cli.MockUi)
c := &ReadCommand{
Meta: meta.Meta{
ClientToken: token,
Ui: ui,
cases := []struct {
name string
args []string
out string
code int
}{
{
"empty",
nil,
"Missing PATH!",
1,
},
{
"slash",
[]string{"/"},
"Missing PATH!",
1,
},
{
"not_found",
[]string{"nope/not/once/never"},
"",
2,
},
{
"default",
[]string{"secret/read/foo"},
"foo",
0,
},
{
"field",
[]string{
"-field", "foo",
"secret/read/foo",
},
"bar",
0,
},
{
"field_not_found",
[]string{
"-field", "not-a-real-field",
"secret/read/foo",
},
"not present in secret",
1,
},
{
"format",
[]string{
"-format", "json",
"secret/read/foo",
},
"{",
0,
},
{
"format_bad",
[]string{
"-format", "nope-not-real",
"secret/read/foo",
},
"Invalid output format",
1,
},
}
args := []string{
"-address", addr,
"secret/nope",
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestRead_field(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.Meta{
ClientToken: token,
Ui: ui,
},
}
args := []string{
"-address", addr,
"-field", "value",
"secret/foo",
}
// 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)
}
// Run the read
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
output := ui.OutputWriter.String()
if output != "bar\n" {
t.Fatalf("unexpectd output:\n%s", output)
}
}
func TestRead_field_notFound(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.Meta{
ClientToken: token,
Ui: ui,
},
}
args := []string{
"-address", addr,
"-field", "nope",
"secret/foo",
}
// 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)
}
// Run the read
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
t.Run("validations", func(t *testing.T) {
t.Parallel()
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
client, closer := testVaultServer(t)
defer closer()
if _, err := client.Logical().Write("secret/read/foo", map[string]interface{}{
"foo": "bar",
}); err != nil {
t.Fatal(err)
}
ui, cmd := testReadCommand(t)
cmd.client = client
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("communication_failure", func(t *testing.T) {
t.Parallel()
client, closer := testVaultServerBad(t)
defer closer()
ui, cmd := testReadCommand(t)
cmd.client = client
code := cmd.Run([]string{
"secret/foo",
})
if exp := 2; code != exp {
t.Errorf("expected %d to be %d", code, exp)
}
expected := "Error reading secret/foo: "
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 := testReadCommand(t)
assertNoTabs(t, cmd)
})
}