From b18d2d37ad907b738a57d868302d7fde1c7e14db Mon Sep 17 00:00:00 2001 From: John-Michael Faircloth Date: Mon, 25 Mar 2024 14:47:24 -0500 Subject: [PATCH] allow syncing with out-of-band changes for kv v1 and v2 (#2207) * fixed resource vault_kv_secret_v2 : not reading data_json * fixed resource vault_kv_secret : not reading data_json * modified resource kv secret v2 test * updated tests for kv secret and kv secret v2 * add changelog --------- Co-authored-by: NightOwl998 --- CHANGELOG.md | 1 + vault/resource_kv_secret.go | 8 +++++ vault/resource_kv_secret_test.go | 43 +++++++++++++++++++++++ vault/resource_kv_secret_v2.go | 7 ++++ vault/resource_kv_secret_v2_test.go | 53 +++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df9427115..61ad5b481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ FEATURES: BUGS: * fix `vault_approle_auth_backend_role_secret_id` regression to handle 404 errors ([#2204](https://github.com/hashicorp/terraform-provider-vault/pull/2204)) +* fix `vault_kv_secret` and `vault_kv_secret_v2` failure to update secret data modified outside terraform ([#1933](https://github.com/hashicorp/terraform-provider-vault/pull/1933)) ## 4.1.0 (Mar 20, 2024) diff --git a/vault/resource_kv_secret.go b/vault/resource_kv_secret.go index 0d666d6f2..0f68e3420 100644 --- a/vault/resource_kv_secret.go +++ b/vault/resource_kv_secret.go @@ -103,6 +103,14 @@ func kvSecretRead(_ context.Context, d *schema.ResourceData, meta interface{}) d log.Printf("[DEBUG] secret: %#v", secret) data := secret.Data + jsonData, err := json.Marshal(data) + if err != nil { + return diag.Errorf("error marshaling JSON for %q: %s", path, err) + } + + if err := d.Set(consts.FieldDataJSON, string(jsonData)); err != nil { + return diag.FromErr(err) + } if err := d.Set(consts.FieldData, serializeDataMapToString(data)); err != nil { return diag.FromErr(err) diff --git a/vault/resource_kv_secret_test.go b/vault/resource_kv_secret_test.go index 6a6af56eb..065d14e22 100644 --- a/vault/resource_kv_secret_test.go +++ b/vault/resource_kv_secret_test.go @@ -63,6 +63,49 @@ func TestAccKVSecret(t *testing.T) { }, }) } +func TestAccKVSecret_UpdateOutsideTerraform(t *testing.T) { + t.Parallel() + resourceName := "vault_kv_secret.test" + mount := acctest.RandomWithPrefix("tf-kvv2") + name := acctest.RandomWithPrefix("tf-secret") + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testKVSecretConfig_basic(mount, name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldPath, fmt.Sprintf("%s/%s", mount, name)), + resource.TestCheckResourceAttr(resourceName, "data.%", "2"), + resource.TestCheckResourceAttr(resourceName, "data.zip", "zap"), + resource.TestCheckResourceAttr(resourceName, "data.foo", "bar"), + assertKVV1Data(resourceName), + ), + }, + { + PreConfig: func() { + client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() + + // Simulate external change using Vault CLI for KV v1 + path := fmt.Sprintf("%s/%s", mount, name) + _, err := client.Logical().Write(path, map[string]interface{}{"testkey3": "testvalue3"}) + if err != nil { + t.Fatalf("error simulating external change; err=%s", err) + } + }, + Config: testKVSecretConfig_basic(mount, name), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldPath, fmt.Sprintf("%s/%s", mount, name)), + resource.TestCheckResourceAttr(resourceName, "data.%", "2"), + resource.TestCheckResourceAttr(resourceName, "data.zip", "zap"), + resource.TestCheckResourceAttr(resourceName, "data.foo", "bar"), + assertKVV1Data(resourceName), + ), + }, + }, + }) +} func kvV1MountConfig(path string) string { ret := fmt.Sprintf(` diff --git a/vault/resource_kv_secret_v2.go b/vault/resource_kv_secret_v2.go index 42bec5b83..37605f67f 100644 --- a/vault/resource_kv_secret_v2.go +++ b/vault/resource_kv_secret_v2.go @@ -287,7 +287,14 @@ func kvSecretV2Read(_ context.Context, d *schema.ResourceData, meta interface{}) log.Printf("[DEBUG] secret: %#v", secret) data := secret.Data["data"] + jsonData, err := json.Marshal(data) + if err != nil { + return diag.Errorf("error marshaling JSON for %q: %s", path, err) + } + if err := d.Set(consts.FieldDataJSON, string(jsonData)); err != nil { + return diag.FromErr(err) + } if v, ok := data.(map[string]interface{}); ok { if err := d.Set(consts.FieldData, serializeDataMapToString(v)); err != nil { return diag.FromErr(err) diff --git a/vault/resource_kv_secret_v2_test.go b/vault/resource_kv_secret_v2_test.go index ab2f71620..74dd80e0c 100644 --- a/vault/resource_kv_secret_v2_test.go +++ b/vault/resource_kv_secret_v2_test.go @@ -215,6 +215,58 @@ func TestAccKVSecretV2_DisableRead(t *testing.T) { }) } +// Fadia u have added this +func TestAccKVSecretV2_UpdateOutsideTerraform(t *testing.T) { + resourceName := "vault_kv_secret_v2.test" + mount := acctest.RandomWithPrefix("tf-kv") + name := acctest.RandomWithPrefix("foo") + + resource.Test(t, resource.TestCase{ + ProviderFactories: providerFactories, + PreCheck: func() { testutil.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testKVSecretV2Config_initial(mount, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, consts.FieldMount, mount), + resource.TestCheckResourceAttr(resourceName, consts.FieldName, name), + resource.TestCheckResourceAttr(resourceName, consts.FieldPath, fmt.Sprintf("%s/data/%s", mount, name)), + resource.TestCheckResourceAttr(resourceName, "delete_all_versions", "true"), + resource.TestCheckResourceAttr(resourceName, "data.zip", "zap"), + resource.TestCheckResourceAttr(resourceName, "data.foo", "bar"), + resource.TestCheckResourceAttr(resourceName, "data.flag", "false"), + resource.TestCheckResourceAttr(resourceName, "metadata.version", "1"), + ), + }, + { + PreConfig: func() { + client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() + + // Simulate external change using Vault CLI + path := fmt.Sprintf("%s/data/%s", mount, name) + _, err := client.Logical().Write(path, map[string]interface{}{"data": map[string]interface{}{"testkey3": "testvalue3"}}) + if err != nil { + t.Fatalf(fmt.Sprintf("error simulating external change; err=%s", err)) + } + + }, + + Config: testKVSecretV2Config_initial(mount, name), + + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "data.zip", "zap"), + resource.TestCheckResourceAttr(resourceName, "data.foo", "bar"), + resource.TestCheckResourceAttr(resourceName, "data.flag", "false"), + resource.TestCheckResourceAttr(resourceName, "data_json", "{\"flag\":false,\"foo\":\"bar\",\"zip\":\"zap\"}"), + //we check that the provider updated vault to match the the terraform config therefor creating a new version the secret. + resource.TestCheckResourceAttr(resourceName, "metadata.version", "3"), + ), + }, + }, + }, + ) +} + func readKVData(t *testing.T, mount, name string) { t.Helper() client := testProvider.Meta().(*provider.ProviderMeta).MustGetClient() @@ -235,6 +287,7 @@ func readKVData(t *testing.T, mount, name string) { if !reflect.DeepEqual(resp.Data["data"], testKVV2Data) { t.Fatalf("kvv2 secret data does not match got: %#+v, want: %#+v", resp.Data["data"], testKVV2Data) } + } func writeKVData(t *testing.T, mount, name string) {