mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	@mitchellh suggested we fork `cli` and switch to that. Since we primarily use the interfaces in `cli`, and the new fork has not changed those, this is (mostly) a drop-in replacement. A small fix will be necessary for Vault Enterprise, I believe.
		
			
				
	
	
		
			300 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: BUSL-1.1
 | 
						|
 | 
						|
package command
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"io"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/go-test/deep"
 | 
						|
	"github.com/hashicorp/cli"
 | 
						|
	"github.com/hashicorp/vault/api"
 | 
						|
)
 | 
						|
 | 
						|
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)
 | 
						|
	}
 | 
						|
}
 |