300 lines
7.0 KiB
Go
300 lines
7.0 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-test/deep"
|
|
"github.com/hashicorp/vault/api"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
func testKVMetadataPatchCommand(tb testing.TB) (*cli.MockUi, *KVMetadataPatchCommand) {
|
|
tb.Helper()
|
|
|
|
ui := cli.NewMockUi()
|
|
return ui, &KVMetadataPatchCommand{
|
|
BaseCommand: &BaseCommand{
|
|
UI: ui,
|
|
},
|
|
}
|
|
}
|
|
|
|
func kvMetadataPatchWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
|
|
t.Helper()
|
|
|
|
return retryKVCommand(t, func() (int, string) {
|
|
ui, cmd := testKVMetadataPatchCommand(t)
|
|
cmd.client = client
|
|
|
|
if stdin != nil {
|
|
cmd.testStdin = stdin
|
|
}
|
|
|
|
code := cmd.Run(args)
|
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
|
|
return code, combined
|
|
})
|
|
}
|
|
|
|
func kvMetadataPutWithRetry(t *testing.T, client *api.Client, args []string, stdin *io.PipeReader) (int, string) {
|
|
t.Helper()
|
|
|
|
return retryKVCommand(t, func() (int, string) {
|
|
ui, cmd := testKVMetadataPutCommand(t)
|
|
cmd.client = client
|
|
|
|
if stdin != nil {
|
|
cmd.testStdin = stdin
|
|
}
|
|
|
|
code := cmd.Run(args)
|
|
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
|
|
|
return code, combined
|
|
})
|
|
}
|
|
|
|
func TestKvMetadataPatchCommand_EmptyArgs(t *testing.T) {
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
if err := client.Sys().Mount("kv/", &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
|
}
|
|
|
|
args := make([]string, 0)
|
|
code, combined := kvMetadataPatchWithRetry(t, client, args, nil)
|
|
|
|
expectedCode := 1
|
|
expectedOutput := "Not enough arguments"
|
|
|
|
if code != expectedCode {
|
|
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", expectedCode, code, args)
|
|
}
|
|
|
|
if !strings.Contains(combined, expectedOutput) {
|
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
|
|
}
|
|
}
|
|
|
|
func TestKvMetadataPatchCommand_Flags(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
out string
|
|
code int
|
|
expectedUpdates map[string]interface{}
|
|
}{
|
|
{
|
|
"cas_required_success",
|
|
[]string{"-cas-required=true"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"cas_required": true,
|
|
},
|
|
},
|
|
{
|
|
"cas_required_invalid",
|
|
[]string{"-cas-required=12345"},
|
|
"invalid boolean value",
|
|
1,
|
|
map[string]interface{}{},
|
|
},
|
|
{
|
|
"custom_metadata_success",
|
|
[]string{"-custom-metadata=baz=ghi"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"custom_metadata": map[string]interface{}{
|
|
"foo": "abc",
|
|
"bar": "def",
|
|
"baz": "ghi",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"remove-custom_metadata",
|
|
[]string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"custom_metadata": map[string]interface{}{
|
|
"bar": "def",
|
|
"baz": "ghi",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"remove-custom_metadata-multiple",
|
|
[]string{"-custom-metadata=baz=ghi", "-remove-custom-metadata=foo", "-remove-custom-metadata=bar"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"custom_metadata": map[string]interface{}{
|
|
"baz": "ghi",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"delete_version_after_success",
|
|
[]string{"-delete-version-after=5s"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"delete_version_after": "5s",
|
|
},
|
|
},
|
|
{
|
|
"delete_version_after_invalid",
|
|
[]string{"-delete-version-after=false"},
|
|
"invalid duration",
|
|
1,
|
|
map[string]interface{}{},
|
|
},
|
|
{
|
|
"max_versions_success",
|
|
[]string{"-max-versions=10"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"max_versions": json.Number("10"),
|
|
},
|
|
},
|
|
{
|
|
"max_versions_invalid",
|
|
[]string{"-max-versions=false"},
|
|
"invalid syntax",
|
|
1,
|
|
map[string]interface{}{},
|
|
},
|
|
{
|
|
"multiple_flags_success",
|
|
[]string{"-max-versions=20", "-custom-metadata=baz=123"},
|
|
"Success!",
|
|
0,
|
|
map[string]interface{}{
|
|
"max_versions": json.Number("20"),
|
|
"custom_metadata": map[string]interface{}{
|
|
"foo": "abc",
|
|
"bar": "def",
|
|
"baz": "123",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
basePath := t.Name() + "/"
|
|
secretPath := basePath + "my-secret"
|
|
metadataPath := basePath + "metadata/" + "my-secret"
|
|
|
|
if err := client.Sys().Mount(basePath, &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
|
}
|
|
|
|
putArgs := []string{"-cas-required=true", "-custom-metadata=foo=abc", "-custom-metadata=bar=def", secretPath}
|
|
code, combined := kvMetadataPutWithRetry(t, client, putArgs, nil)
|
|
|
|
if code != 0 {
|
|
t.Fatalf("initial metadata put failed, code: %d, output: %s", code, combined)
|
|
}
|
|
|
|
initialMetadata, err := client.Logical().Read(metadataPath)
|
|
if err != nil {
|
|
t.Fatalf("metadata read failed, err: %#v", err)
|
|
}
|
|
|
|
patchArgs := append(tc.args, secretPath)
|
|
|
|
code, combined = kvMetadataPatchWithRetry(t, client, patchArgs, nil)
|
|
|
|
if !strings.Contains(combined, tc.out) {
|
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", tc.out, combined, patchArgs)
|
|
}
|
|
if code != tc.code {
|
|
t.Fatalf("expected code to be %d but was %d for patch cmd with args %#v", tc.code, code, patchArgs)
|
|
}
|
|
|
|
patchedMetadata, err := client.Logical().Read(metadataPath)
|
|
if err != nil {
|
|
t.Fatalf("metadata read failed, err: %#v", err)
|
|
}
|
|
|
|
for k, v := range patchedMetadata.Data {
|
|
var expectedVal interface{}
|
|
|
|
if inputVal, ok := tc.expectedUpdates[k]; ok {
|
|
expectedVal = inputVal
|
|
} else {
|
|
expectedVal = initialMetadata.Data[k]
|
|
}
|
|
|
|
if diff := deep.Equal(expectedVal, v); len(diff) > 0 {
|
|
t.Fatalf("patched %q mismatch, diff: %#v", k, diff)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKvMetadataPatchCommand_CasWarning(t *testing.T) {
|
|
client, closer := testVaultServer(t)
|
|
defer closer()
|
|
|
|
basePath := "kv/"
|
|
if err := client.Sys().Mount(basePath, &api.MountInput{
|
|
Type: "kv-v2",
|
|
}); err != nil {
|
|
t.Fatalf("kv-v2 mount error: %#v", err)
|
|
}
|
|
|
|
secretPath := basePath + "my-secret"
|
|
|
|
args := []string{"-cas-required=true", secretPath}
|
|
code, combined := kvMetadataPutWithRetry(t, client, args, nil)
|
|
|
|
if code != 0 {
|
|
t.Fatalf("metadata put failed, code: %d, output: %s", code, combined)
|
|
}
|
|
|
|
casConfig := map[string]interface{}{
|
|
"cas_required": true,
|
|
}
|
|
|
|
_, err := client.Logical().Write(basePath+"config", casConfig)
|
|
if err != nil {
|
|
t.Fatalf("config write failed, err: #%v", err)
|
|
}
|
|
|
|
args = []string{"-cas-required=false", secretPath}
|
|
code, combined = kvMetadataPatchWithRetry(t, client, args, nil)
|
|
|
|
if code != 0 {
|
|
t.Fatalf("expected code to be 0 but was %d for patch cmd with args %#v", code, args)
|
|
}
|
|
|
|
expectedOutput := "\"cas_required\" set to false, but is mandated by backend config"
|
|
if !strings.Contains(combined, expectedOutput) {
|
|
t.Fatalf("expected output to be %q but was %q for patch cmd with args %#v", expectedOutput, combined, args)
|
|
}
|
|
}
|