From 22f51dc6d609b01168ef5dbdb7333c235f427915 Mon Sep 17 00:00:00 2001 From: Hamid Ghaf <83242695+hghaf099@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:11:36 -0500 Subject: [PATCH] improve kv CLI to remove data or custom metadata using kv patch (#18067) * improve kv CLI to remove data or custom metadata using kv patch * CL * adding a comment --- changelog/18067.txt | 3 ++ command/kv_metadata_patch.go | 39 ++++++++++++++++++------ command/kv_metadata_patch_test.go | 23 ++++++++++++++ command/kv_patch.go | 32 ++++++++++++++++--- vault/external_tests/kv/kv_patch_test.go | 6 ++++ 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 changelog/18067.txt diff --git a/changelog/18067.txt b/changelog/18067.txt new file mode 100644 index 000000000..2c86305d9 --- /dev/null +++ b/changelog/18067.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli/kv: improve kv CLI to remove data or custom metadata using kv patch +``` diff --git a/command/kv_metadata_patch.go b/command/kv_metadata_patch.go index b63da2369..11ffdb4be 100644 --- a/command/kv_metadata_patch.go +++ b/command/kv_metadata_patch.go @@ -20,12 +20,13 @@ var ( type KVMetadataPatchCommand struct { *BaseCommand - flagMaxVersions int - flagCASRequired BoolPtr - flagDeleteVersionAfter time.Duration - flagCustomMetadata map[string]string - flagMount string - testStdin io.Reader // for tests + flagMaxVersions int + flagCASRequired BoolPtr + flagDeleteVersionAfter time.Duration + flagCustomMetadata map[string]string + flagRemoveCustomMetadata []string + flagMount string + testStdin io.Reader // for tests } func (c *KVMetadataPatchCommand) Synopsis() string { @@ -65,6 +66,10 @@ Usage: vault kv metadata patch [options] KEY $ vault kv metadata patch -mount=secret -custom-metadata=foo=abc -custom-metadata=bar=123 foo + To remove custom meta data from the corresponding path in the key-value store, kv metadata patch can be used. + + $ vault kv metadata patch -mount=secret -remove-custom-metadata=bar foo + Additional flags and more advanced use cases are detailed below. ` + c.Flags().Help() @@ -111,6 +116,13 @@ func (c *KVMetadataPatchCommand) Flags() *FlagSets { This can be specified multiple times to add multiple pieces of metadata.`, }) + f.StringSliceVar(&StringSliceVar{ + Name: "remove-custom-metadata", + Target: &c.flagRemoveCustomMetadata, + Default: []string{}, + Usage: "Key to remove from custom metadata. To specify multiple values, specify this flag multiple times.", + }) + f.StringVar(&StringVar{ Name: "mount", Target: &c.flagMount, @@ -198,7 +210,7 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { fullPath := addPrefixToKVPath(partialPath, mountPath, "metadata") - data := map[string]interface{}{} + data := make(map[string]interface{}, 0) if c.flagMaxVersions >= 0 { data["max_versions"] = c.flagMaxVersions @@ -212,10 +224,19 @@ func (c *KVMetadataPatchCommand) Run(args []string) int { data["delete_version_after"] = c.flagDeleteVersionAfter.String() } - if len(c.flagCustomMetadata) > 0 { - data["custom_metadata"] = c.flagCustomMetadata + customMetadata := make(map[string]interface{}) + + for key, value := range c.flagCustomMetadata { + customMetadata[key] = value } + for _, key := range c.flagRemoveCustomMetadata { + // A null in a JSON merge patch payload will remove the associated key + customMetadata[key] = nil + } + + data["custom_metadata"] = customMetadata + secret, err := client.Logical().JSONMergePatch(context.Background(), fullPath, data) if err != nil { c.UI.Error(fmt.Sprintf("Error writing data to %s: %s", fullPath, err)) diff --git a/command/kv_metadata_patch_test.go b/command/kv_metadata_patch_test.go index 40b74dc8d..3b15c5202 100644 --- a/command/kv_metadata_patch_test.go +++ b/command/kv_metadata_patch_test.go @@ -122,6 +122,29 @@ func TestKvMetadataPatchCommand_Flags(t *testing.T) { }, }, }, + { + "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"}, diff --git a/command/kv_patch.go b/command/kv_patch.go index 6a736fa24..3d0f3456c 100644 --- a/command/kv_patch.go +++ b/command/kv_patch.go @@ -21,10 +21,11 @@ var ( type KVPatchCommand struct { *BaseCommand - flagCAS int - flagMethod string - flagMount string - testStdin io.Reader // for tests + flagCAS int + flagMethod string + flagMount string + testStdin io.Reader // for tests + flagRemoveData []string } func (c *KVPatchCommand) Synopsis() string { @@ -76,6 +77,10 @@ Usage: vault kv patch [options] KEY [DATA] $ vault kv patch -mount=secret -method=rw foo bar=baz + To remove data from the corresponding path in the key-value store, kv patch can be used. + + $ vault kv patch -mount=secret -remove-data=bar foo + Additional flags and more advanced use cases are detailed below. ` + c.Flags().Help() @@ -117,6 +122,13 @@ func (c *KVPatchCommand) Flags() *FlagSets { v2 secrets.`, }) + f.StringSliceVar(&StringSliceVar{ + Name: "remove-data", + Target: &c.flagRemoveData, + Default: []string{}, + Usage: "Key to remove from data. To specify multiple values, specify this flag multiple times.", + }) + return set } @@ -147,7 +159,7 @@ func (c *KVPatchCommand) Run(args []string) int { case len(args) < 1: c.UI.Error(fmt.Sprintf("Not enough arguments (expected >1, got %d)", len(args))) return 1 - case len(args) == 1: + case len(c.flagRemoveData) == 0 && len(args) == 1: c.UI.Error("Must supply data") return 1 } @@ -211,6 +223,16 @@ func (c *KVPatchCommand) Run(args []string) int { return 2 } + // collecting data to be removed + if newData == nil { + newData = make(map[string]interface{}) + } + + for _, key := range c.flagRemoveData { + // A null in a JSON merge patch payload will remove the associated key + newData[key] = nil + } + // Check the method and behave accordingly var secret *api.Secret var code int diff --git a/vault/external_tests/kv/kv_patch_test.go b/vault/external_tests/kv/kv_patch_test.go index 52e60215d..2228097ca 100644 --- a/vault/external_tests/kv/kv_patch_test.go +++ b/vault/external_tests/kv/kv_patch_test.go @@ -249,6 +249,7 @@ func TestKV_Patch_RootToken(t *testing.T) { data := map[string]interface{}{ "data": map[string]interface{}{ "bar": "baz", + "foo": "qux", }, } @@ -263,6 +264,7 @@ func TestKV_Patch_RootToken(t *testing.T) { data := map[string]interface{}{ "data": map[string]interface{}{ "bar": "quux", + "foo": nil, }, } return client.Logical().JSONMergePatch(context.Background(), "kv/data/foo", data) @@ -288,4 +290,8 @@ func TestKV_Patch_RootToken(t *testing.T) { if bar != "quux" { t.Fatalf("expected bar to be quux but it was %q", bar) } + + if _, ok := secret.Data["data"].(map[string]interface{})["foo"]; ok { + t.Fatalf("expected data not to include foo") + } }