555 lines
11 KiB
Go
555 lines
11 KiB
Go
package command
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
func testKVPutCommand(tb testing.TB) (*cli.MockUi, *KVPutCommand) {
|
|
tb.Helper()
|
|
|
|
ui := cli.NewMockUi()
|
|
return ui, &KVPutCommand{
|
|
BaseCommand: &BaseCommand{
|
|
UI: ui,
|
|
},
|
|
}
|
|
}
|
|
|
|
func retryKVCommand(t *testing.T, client *api.Client, args []string) (code int, combined string) {
|
|
t.Helper()
|
|
|
|
// Loop until return message does not indicate upgrade, or timeout.
|
|
timeout := time.After(20 * time.Second)
|
|
for {
|
|
ui, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
code = cmd.Run(args)
|
|
combined = ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
|
|
// This is an error if a v1 mount, but test case case doesn't
|
|
// currently contain the information to know the difference.
|
|
if strings.Contains(combined, "Upgrading from non-versioned to versioned") {
|
|
select {
|
|
case <-timeout:
|
|
t.Errorf("timeout expired waiting for upgrade: %q", combined)
|
|
return code, combined
|
|
default:
|
|
}
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return code, combined
|
|
}
|
|
|
|
func TestKVPutCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
out string
|
|
code int
|
|
}{
|
|
{
|
|
"not_enough_args",
|
|
[]string{},
|
|
"Not enough arguments",
|
|
1,
|
|
},
|
|
{
|
|
"empty_kvs",
|
|
[]string{"secret/write/foo"},
|
|
"Must supply data",
|
|
1,
|
|
},
|
|
{
|
|
"kvs_no_value",
|
|
[]string{"secret/write/foo", "foo"},
|
|
"Failed to parse K=V data",
|
|
1,
|
|
},
|
|
{
|
|
"single_value",
|
|
[]string{"secret/write/foo", "foo=bar"},
|
|
"Success!",
|
|
0,
|
|
},
|
|
{
|
|
"multi_value",
|
|
[]string{"secret/write/foo", "foo=bar", "zip=zap"},
|
|
"Success!",
|
|
0,
|
|
},
|
|
{
|
|
"v2_single_value",
|
|
[]string{"kv/write/foo", "foo=bar"},
|
|
"created_time",
|
|
0,
|
|
},
|
|
{
|
|
"v2_multi_value",
|
|
[]string{"kv/write/foo", "foo=bar", "zip=zap"},
|
|
"created_time",
|
|
0,
|
|
},
|
|
}
|
|
|
|
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.Sys().Mount("kv/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
code, combined := retryKVCommand(t, client, tc.args)
|
|
if code != tc.code {
|
|
t.Errorf("expected %d to be %d", code, tc.code)
|
|
}
|
|
if !strings.Contains(combined, tc.out) {
|
|
t.Errorf("expected %q to contain %q", combined, tc.out)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("v2_cas", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
if err := client.Sys().Mount("kv/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Only have to potentially retry the first time.
|
|
code, combined := retryKVCommand(t, client, []string{
|
|
"-cas", "0", "kv/write/cas", "bar=baz",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
if !strings.Contains(combined, "created_time") {
|
|
t.Errorf("expected %q to contain %q", combined, "created_time")
|
|
}
|
|
|
|
ui, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
code = cmd.Run([]string{
|
|
"-cas", "1", "kv/write/cas", "bar=baz",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
combined = ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
if !strings.Contains(combined, "created_time") {
|
|
t.Errorf("expected %q to contain %q", combined, "created_time")
|
|
}
|
|
|
|
ui, cmd = testKVPutCommand(t)
|
|
cmd.client = client
|
|
code = cmd.Run([]string{
|
|
"-cas", "1", "kv/write/cas", "bar=baz",
|
|
})
|
|
if code != 2 {
|
|
t.Fatalf("expected 2 to be %d", code)
|
|
}
|
|
combined = ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
if !strings.Contains(combined, "check-and-set parameter did not match the current version") {
|
|
t.Errorf("expected %q to contain %q", combined, "check-and-set parameter did not match the current version")
|
|
}
|
|
})
|
|
|
|
t.Run("v1_data", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
ui, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
|
|
code := cmd.Run([]string{
|
|
"secret/write/data", "bar=baz",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
if !strings.Contains(combined, "Success!") {
|
|
t.Errorf("expected %q to contain %q", combined, "created_time")
|
|
}
|
|
|
|
ui, rcmd := testReadCommand(t)
|
|
rcmd.client = client
|
|
code = rcmd.Run([]string{
|
|
"secret/write/data",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
combined = ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
if strings.Contains(combined, "data") {
|
|
t.Errorf("expected %q not to contain %q", combined, "data")
|
|
}
|
|
})
|
|
|
|
t.Run("stdin_full", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
stdinR, stdinW := io.Pipe()
|
|
go func() {
|
|
stdinW.Write([]byte(`{"foo":"bar"}`))
|
|
stdinW.Close()
|
|
}()
|
|
|
|
_, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
cmd.testStdin = stdinR
|
|
|
|
code := cmd.Run([]string{
|
|
"secret/write/stdin_full", "-",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
|
|
secret, err := client.Logical().Read("secret/write/stdin_full")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if secret == nil || secret.Data == nil {
|
|
t.Fatal("expected secret to have data")
|
|
}
|
|
if exp, act := "bar", secret.Data["foo"].(string); exp != act {
|
|
t.Errorf("expected %q to be %q", act, exp)
|
|
}
|
|
})
|
|
|
|
t.Run("stdin_value", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
stdinR, stdinW := io.Pipe()
|
|
go func() {
|
|
stdinW.Write([]byte("bar"))
|
|
stdinW.Close()
|
|
}()
|
|
|
|
_, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
cmd.testStdin = stdinR
|
|
|
|
code := cmd.Run([]string{
|
|
"secret/write/stdin_value", "foo=-",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
|
|
secret, err := client.Logical().Read("secret/write/stdin_value")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if secret == nil || secret.Data == nil {
|
|
t.Fatal("expected secret to have data")
|
|
}
|
|
if exp, act := "bar", secret.Data["foo"].(string); exp != act {
|
|
t.Errorf("expected %q to be %q", act, exp)
|
|
}
|
|
})
|
|
|
|
t.Run("integration", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
_, cmd := testKVPutCommand(t)
|
|
cmd.client = client
|
|
|
|
code := cmd.Run([]string{
|
|
"secret/write/integration", "foo=bar", "zip=zap",
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("expected 0 to be %d", code)
|
|
}
|
|
|
|
secret, err := client.Logical().Read("secret/write/integration")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if secret == nil || secret.Data == nil {
|
|
t.Fatal("expected secret to have data")
|
|
}
|
|
if exp, act := "bar", secret.Data["foo"].(string); exp != act {
|
|
t.Errorf("expected %q to be %q", act, exp)
|
|
}
|
|
if exp, act := "zap", secret.Data["zip"].(string); exp != act {
|
|
t.Errorf("expected %q to be %q", act, exp)
|
|
}
|
|
})
|
|
|
|
t.Run("no_tabs", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, cmd := testKVPutCommand(t)
|
|
assertNoTabs(t, cmd)
|
|
})
|
|
}
|
|
|
|
func testKVGetCommand(tb testing.TB) (*cli.MockUi, *KVGetCommand) {
|
|
tb.Helper()
|
|
|
|
ui := cli.NewMockUi()
|
|
return ui, &KVGetCommand{
|
|
BaseCommand: &BaseCommand{
|
|
UI: ui,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestKVGetCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
out string
|
|
code int
|
|
}{
|
|
{
|
|
"not_enough_args",
|
|
[]string{},
|
|
"Not enough arguments",
|
|
1,
|
|
},
|
|
{
|
|
"too_many_args",
|
|
[]string{"foo", "bar"},
|
|
"Too many arguments",
|
|
1,
|
|
},
|
|
{
|
|
"not_found",
|
|
[]string{"secret/nope/not/once/never"},
|
|
"",
|
|
2,
|
|
},
|
|
{
|
|
"default",
|
|
[]string{"secret/read/foo"},
|
|
"foo",
|
|
0,
|
|
},
|
|
{
|
|
"v1_field",
|
|
[]string{"-field", "foo", "secret/read/foo"},
|
|
"bar",
|
|
0,
|
|
},
|
|
{
|
|
"v2_field",
|
|
[]string{"-field", "foo", "kv/read/foo"},
|
|
"bar",
|
|
0,
|
|
},
|
|
|
|
{
|
|
"v2_not_found",
|
|
[]string{"kv/nope/not/once/never"},
|
|
"",
|
|
2,
|
|
},
|
|
|
|
{
|
|
"v2_read",
|
|
[]string{"kv/read/foo"},
|
|
"foo",
|
|
0,
|
|
},
|
|
{
|
|
"v2_read",
|
|
[]string{"kv/read/foo"},
|
|
"version",
|
|
0,
|
|
},
|
|
{
|
|
"v2_read_version",
|
|
[]string{"--version", "1", "kv/read/foo"},
|
|
"foo",
|
|
0,
|
|
},
|
|
}
|
|
|
|
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.Sys().Mount("kv/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Give time for the upgrade code to run/finish
|
|
time.Sleep(time.Second)
|
|
|
|
if _, err := client.Logical().Write("secret/read/foo", map[string]interface{}{
|
|
"foo": "bar",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := client.Logical().Write("kv/data/read/foo", map[string]interface{}{
|
|
"data": map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui, cmd := testKVGetCommand(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("no_tabs", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, cmd := testKVGetCommand(t)
|
|
assertNoTabs(t, cmd)
|
|
})
|
|
}
|
|
|
|
func testKVMetadataGetCommand(tb testing.TB) (*cli.MockUi, *KVMetadataGetCommand) {
|
|
tb.Helper()
|
|
|
|
ui := cli.NewMockUi()
|
|
return ui, &KVMetadataGetCommand{
|
|
BaseCommand: &BaseCommand{
|
|
UI: ui,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestKVMetadataGetCommand(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
out string
|
|
code int
|
|
}{
|
|
{
|
|
"v1",
|
|
[]string{"secret/foo"},
|
|
"Metadata not supported on KV Version 1",
|
|
1,
|
|
},
|
|
{
|
|
"metadata_exists",
|
|
[]string{"kv/foo"},
|
|
"current_version",
|
|
0,
|
|
},
|
|
{
|
|
"versions_exist",
|
|
[]string{"kv/foo"},
|
|
"deletion_time",
|
|
0,
|
|
},
|
|
}
|
|
|
|
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.Sys().Mount("kv/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Give time for the upgrade code to run/finish
|
|
time.Sleep(time.Second)
|
|
|
|
if _, err := client.Logical().Write("kv/data/foo", map[string]interface{}{
|
|
"data": map[string]interface{}{
|
|
"foo": "bar",
|
|
},
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ui, cmd := testKVMetadataGetCommand(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("no_tabs", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, cmd := testKVMetadataGetCommand(t)
|
|
assertNoTabs(t, cmd)
|
|
})
|
|
}
|