Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/46899.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_cloudfrontkeyvaluestore_keys_exclusive: Fix issue where values were incorrectly JSON-encoded, resulting in extra quotes being stored in AWS
```
50 changes: 50 additions & 0 deletions internal/service/cloudfrontkeyvaluestore/keys_exclusive.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ func (r *keysExclusiveResource) syncKeyValuePairs(ctx context.Context, plan *key
return diags
}

// Manually set Value fields to avoid JSON encoding by AutoFlEx.
// Build a map from Key to Value from the plan
planValueMap := make(map[string]string)
var planModels []resourceKeyValuePairModel
diags.Append(plan.ResourceKeyValuePair.ElementsAs(ctx, &planModels, false)...)
if !diags.HasError() {
for _, model := range planModels {
planValueMap[model.Key.ValueString()] = model.Value.ValueString()
}
// Update the Value fields in want using the map
for i := range want {
if key := aws.ToString(want[i].Key); key != "" {
if value, ok := planValueMap[key]; ok {
want[i].Value = aws.String(value)
}
}
}
}

put, del, modify, _ := intflex.DiffSlicesWithModify(have, want, resourceKeyValuePairEqual, resourceKeyValuePairKeyEqual)
put = append(put, modify...)

Expand Down Expand Up @@ -231,6 +250,37 @@ func (r *keysExclusiveResource) Read(ctx context.Context, request resource.ReadR
return
}

// Manually set Value fields to avoid JSON decoding by AutoFlEx.
// Build a map from Key to Value from the AWS response
awsValueMap := make(map[string]string)
for _, pair := range keyPairs {
if key := aws.ToString(pair.Key); key != "" {
awsValueMap[key] = aws.ToString(pair.Value)
}
}
// Update the Value fields in data using the map
var dataModels []resourceKeyValuePairModel
response.Diagnostics.Append(data.ResourceKeyValuePair.ElementsAs(ctx, &dataModels, false)...)
if !response.Diagnostics.HasError() {
for i := range dataModels {
if key := dataModels[i].Key.ValueString(); key != "" {
if value, ok := awsValueMap[key]; ok {
dataModels[i].Value = types.StringValue(value)
}
}
}
// Set the updated models back - convert to slice of pointers
dataModelPtrs := make([]*resourceKeyValuePairModel, len(dataModels))
for i := range dataModels {
dataModelPtrs[i] = &dataModels[i]
}
updatedSet, diag := fwtypes.NewSetNestedObjectValueOfSlice(ctx, dataModelPtrs, nil)
response.Diagnostics.Append(diag...)
if !response.Diagnostics.HasError() {
data.ResourceKeyValuePair = updatedSet
}
}

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

Expand Down
80 changes: 80 additions & 0 deletions internal/service/cloudfrontkeyvaluestore/keys_exclusive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,49 @@ func TestAccCloudFrontKeyValueStoreKeysExclusive_valueUpdate(t *testing.T) {
})
}

func TestAccCloudFrontKeyValueStoreKeysExclusive_specialCharacters(t *testing.T) {
ctx := acctest.Context(t)
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
key1 := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
key2 := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
value1 := `special "quoted" value with 'quotes' and {braces}`
value2 := `another [bracketed] value with \backslashes\ and:colons`

resourceName := "aws_cloudfrontkeyvaluestore_keys_exclusive.test"

acctest.ParallelTest(ctx, t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckPartitionHasService(t, names.CloudFront)
},
ErrorCheck: acctest.ErrorCheck(t, names.CloudFront),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckKeysExclusiveDestroy(ctx, t),
Steps: []resource.TestStep{
{
Config: testAccKeysExclusiveConfig_basic([]string{key1, key2}, []string{value1, value2}, rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckKeysExclusiveExists(ctx, t, resourceName),
// Verify values in Terraform state
testCheckMultipleKeyValuePairs([]string{key1, key2}, []string{value1, value2}, resourceName),
// Verify actual values in AWS (not JSON-encoded)
testAccCheckKeysExclusiveHasValues(ctx, t, resourceName, map[string]string{
key1: value1,
key2: value2,
}),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "key_value_store_arn"),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "key_value_store_arn",
},
},
})
}

func testAccCheckKeysExclusiveDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.ProviderMeta(ctx, t).CloudFrontKeyValueStoreClient(ctx)
Expand Down Expand Up @@ -415,6 +458,43 @@ func testAccCheckKeysExclusiveExists(ctx context.Context, t *testing.T, n string
}
}

func testAccCheckKeysExclusiveHasValues(ctx context.Context, t *testing.T, n string, expectedValues map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

conn := acctest.ProviderMeta(ctx, t).CloudFrontKeyValueStoreClient(ctx)

_, keyPairs, err := tfcloudfrontkeyvaluestore.FindResourceKeyValuePairsForKeyValueStore(ctx, conn, rs.Primary.Attributes["key_value_store_arn"])
if err != nil {
return err
}

// Build actual values map from AWS
actualValues := make(map[string]string)
for _, pair := range keyPairs {
if pair.Key != nil && pair.Value != nil {
actualValues[*pair.Key] = *pair.Value
}
}

// Verify each expected value matches actual value in AWS
for expectedKey, expectedValue := range expectedValues {
actualValue, ok := actualValues[expectedKey]
if !ok {
return fmt.Errorf("CloudFront KeyValueStore missing key %q", expectedKey)
}
if actualValue != expectedValue {
return fmt.Errorf("CloudFront KeyValueStore key %q value = %q, expected %q", expectedKey, actualValue, expectedValue)
}
}

return nil
}
}

func testAccCheckKeyValueStoreKeysExclusiveUpdate(ctx context.Context, t *testing.T, n string, deletes []types.DeleteKeyRequestListItem, puts []types.PutKeyRequestListItem) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down
Loading