From 9f4eafbcdc1eeb6a3d2f8c1f23f64c22dd2a3ce8 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 14 Jun 2024 16:33:24 -0700 Subject: [PATCH 1/7] Adds validation for `ttl` subfields --- internal/service/dynamodb/table.go | 70 ++++++++++++- internal/service/dynamodb/table_test.go | 127 ++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/internal/service/dynamodb/table.go b/internal/service/dynamodb/table.go index f43c6044f757..edbcb7bb1eac 100644 --- a/internal/service/dynamodb/table.go +++ b/internal/service/dynamodb/table.go @@ -17,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" awstypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" @@ -116,6 +117,7 @@ func resourceTable() *schema.Resource { // https://github.com/hashicorp/terraform-provider-aws/issues/25214 return old.(string) != new.(string) && new.(string) != "" }), + validateTTLCustomDiff, verify.SetTagsDiff, ), @@ -450,7 +452,7 @@ func resourceTable() *schema.Resource { Schema: map[string]*schema.Schema{ "attribute_name": { Type: schema.TypeString, - Required: true, + Optional: true, }, names.AttrEnabled: { Type: schema.TypeBool, @@ -2389,3 +2391,69 @@ func validateProvisionedThroughputField(diff *schema.ResourceDiff, key string) e } return nil } + +func validateTTLCustomDiff(ctx context.Context, d *schema.ResourceDiff, meta any) error { + var diags diag.Diagnostics + + configRaw := d.GetRawConfig() + if !configRaw.IsKnown() || configRaw.IsNull() { + return nil + } + + ttlPath := cty.GetAttrPath("ttl") + ttl := configRaw.GetAttr("ttl") + if ttl.IsKnown() && !ttl.IsNull() { + listenerActionsPlantimeValidate(ttlPath, ttl, &diags) + } + + return sdkdiag.DiagnosticsError(diags) +} + +func listenerActionsPlantimeValidate(ttlPath cty.Path, ttl cty.Value, diags *diag.Diagnostics) { + it := ttl.ElementIterator() + for it.Next() { + i, action := it.Element() + ttlPath := ttlPath.Index(i) + + listenerActionPlantimeValidate(ttlPath, action, diags) + } +} + +func listenerActionPlantimeValidate(ttlPath cty.Path, ttl cty.Value, diags *diag.Diagnostics) { + attribute := ttl.GetAttr("attribute_name") + if !attribute.IsKnown() { + return + } + + enabled := ttl.GetAttr(names.AttrEnabled) + if !enabled.IsKnown() { + return + } + if enabled.IsNull() { + return + } + + if enabled.True() { + if attribute.IsNull() { + *diags = append(*diags, errs.NewAttributeRequiredWhenError( + ttlPath.GetAttr("attribute_name"), + ttlPath.GetAttr(names.AttrEnabled), + "true", + )) + } else if attribute.AsString() == "" { + *diags = append(*diags, errs.NewInvalidValueAttributeErrorf( + ttlPath.GetAttr("attribute_name"), + "Attribute %q cannot have an empty value", + errs.PathString(ttlPath.GetAttr("attribute_name")), + )) + } + } else { + if !(attribute.IsNull() || attribute.AsString() == "") { + *diags = append(*diags, errs.NewAttributeConflictsWhenError( + ttlPath.GetAttr("attribute_name"), + ttlPath.GetAttr(names.AttrEnabled), + "false", + )) + } + } +} diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index ffa84f497c91..606cd60f7f4c 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -1374,6 +1374,11 @@ func TestAccDynamoDBTable_TTL_enabled(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccTableConfig_timeToLive_unset(rName), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, }, }) } @@ -1405,11 +1410,49 @@ func TestAccDynamoDBTable_TTL_disabled(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccTableConfig_timeToLive_unset(rName), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + }, + }) +} + +// TTL tests must be split since it can only be updated once per hour +// ValidationException: Time to live has been modified multiple times within a fixed interval +func TestAccDynamoDBTable_TTL_update(t *testing.T) { + ctx := acctest.Context(t) + var table awstypes.TableDescription + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.DynamoDBServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTableConfig_timeToLive(rName, false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableExists(ctx, resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", ""), + resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtFalse), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccTableConfig_timeToLive(rName, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", "TestTTL"), resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtTrue), ), }, @@ -1417,6 +1460,32 @@ func TestAccDynamoDBTable_TTL_disabled(t *testing.T) { }) } +func TestAccDynamoDBTable_TTL_validate(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.DynamoDBServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTableConfig_timeToLive_AttributeName(rName, "TestTTL", false), + ExpectError: regexache.MustCompile(regexp.QuoteMeta(`Attribute "ttl[0].attribute_name" cannot be specified when "ttl[0].enabled" is "false"`)), + }, + { + Config: testAccTableConfig_timeToLive_AttributeName(rName, "", true), + ExpectError: regexache.MustCompile(regexp.QuoteMeta(`Attribute "ttl[0].attribute_name" cannot have an empty value`)), + }, + { + Config: testAccTableConfig_TTL_missingAttributeName(rName, true), + ExpectError: regexache.MustCompile(regexp.QuoteMeta(`Attribute "ttl[0].attribute_name" cannot have an empty value`)), + }, + }, + }) +} + func TestAccDynamoDBTable_attributeUpdate(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -3517,6 +3586,64 @@ resource "aws_dynamodb_table" "test" { `, rName, ttlEnabled) } +func testAccTableConfig_timeToLive_unset(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + hash_key = "TestTableHashKey" + name = %[1]q + read_capacity = 1 + write_capacity = 1 + + attribute { + name = "TestTableHashKey" + type = "S" + } +} +`, rName) +} + +func testAccTableConfig_timeToLive_AttributeName(rName, ttlAttribute string, ttlEnabled bool) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + hash_key = "TestTableHashKey" + name = %[1]q + read_capacity = 1 + write_capacity = 1 + + attribute { + name = "TestTableHashKey" + type = "S" + } + + ttl { + attribute_name = %[2]q + enabled = %[3]t + } +} +`, rName, ttlAttribute, ttlEnabled) +} + +func testAccTableConfig_TTL_missingAttributeName(rName string, ttlEnabled bool) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + hash_key = "TestTableHashKey" + name = %[1]q + read_capacity = 1 + write_capacity = 1 + + attribute { + name = "TestTableHashKey" + type = "S" + } + + ttl { + attribute_name = "" + enabled = %[2]t + } +} +`, rName, ttlEnabled) +} + func testAccTableConfig_oneAttribute(rName, hashKey, attrName, attrType string) string { return fmt.Sprintf(` resource "aws_dynamodb_table" "test" { From 6348f4bf3259aa18ec3ce5c258c4adf7089a60db Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 14 Jun 2024 16:35:16 -0700 Subject: [PATCH 2/7] Updates documentation --- website/docs/r/dynamodb_table.html.markdown | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index a708e401e50e..ab4672b0dbe8 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -55,7 +55,7 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" { ttl { attribute_name = "TimeToExist" - enabled = false + enabled = true } global_secondary_index { @@ -261,8 +261,10 @@ Optional arguments: ### `ttl` -* `enabled` - (Required) Whether TTL is enabled. -* `attribute_name` - (Required) Name of the table attribute to store the TTL timestamp in. +* `attribute_name` - (Optional) Name of the table attribute to store the TTL timestamp in. + Required if `enabled` is `true`, must not be set otherwise. +* `enabled` - (Optional) Whether TTL is enabled. + Default value is `false`. ## Attribute Reference From ae9896db9cb6e51c3064ee95bc945691b36dc814 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 14 Jun 2024 17:11:26 -0700 Subject: [PATCH 3/7] Reorganizes tests --- internal/service/dynamodb/table_test.go | 45 +++++++------------------ 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index 606cd60f7f4c..1ed8833f5540 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -1362,10 +1362,11 @@ func TestAccDynamoDBTable_TTL_enabled(t *testing.T) { CheckDestroy: testAccCheckTableDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTableConfig_timeToLive(rName, true), + Config: testAccTableConfig_timeToLive(rName, rName, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", rName), resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtTrue), ), }, @@ -1398,10 +1399,11 @@ func TestAccDynamoDBTable_TTL_disabled(t *testing.T) { CheckDestroy: testAccCheckTableDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTableConfig_timeToLive(rName, false), + Config: testAccTableConfig_timeToLive(rName, "", false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", ""), resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtFalse), ), }, @@ -1434,7 +1436,7 @@ func TestAccDynamoDBTable_TTL_update(t *testing.T) { CheckDestroy: testAccCheckTableDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTableConfig_timeToLive(rName, false), + Config: testAccTableConfig_timeToLive(rName, "", false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), @@ -1448,11 +1450,11 @@ func TestAccDynamoDBTable_TTL_update(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccTableConfig_timeToLive(rName, true), + Config: testAccTableConfig_timeToLive(rName, rName, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", "TestTTL"), + resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", rName), resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtTrue), ), }, @@ -1471,11 +1473,11 @@ func TestAccDynamoDBTable_TTL_validate(t *testing.T) { CheckDestroy: testAccCheckTableDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccTableConfig_timeToLive_AttributeName(rName, "TestTTL", false), + Config: testAccTableConfig_timeToLive(rName, "TestTTL", false), ExpectError: regexache.MustCompile(regexp.QuoteMeta(`Attribute "ttl[0].attribute_name" cannot be specified when "ttl[0].enabled" is "false"`)), }, { - Config: testAccTableConfig_timeToLive_AttributeName(rName, "", true), + Config: testAccTableConfig_timeToLive(rName, "", true), ExpectError: regexache.MustCompile(regexp.QuoteMeta(`Attribute "ttl[0].attribute_name" cannot have an empty value`)), }, { @@ -3565,7 +3567,7 @@ resource "aws_dynamodb_table" "test" { `, rName) } -func testAccTableConfig_timeToLive(rName string, ttlEnabled bool) string { +func testAccTableConfig_timeToLive(rName, ttlAttribute string, ttlEnabled bool) string { return fmt.Sprintf(` resource "aws_dynamodb_table" "test" { hash_key = "TestTableHashKey" @@ -3579,11 +3581,11 @@ resource "aws_dynamodb_table" "test" { } ttl { - attribute_name = %[2]t ? "TestTTL" : "" - enabled = %[2]t + attribute_name = %[2]q + enabled = %[3]t } } -`, rName, ttlEnabled) +`, rName, ttlAttribute, ttlEnabled) } func testAccTableConfig_timeToLive_unset(rName string) string { @@ -3602,27 +3604,6 @@ resource "aws_dynamodb_table" "test" { `, rName) } -func testAccTableConfig_timeToLive_AttributeName(rName, ttlAttribute string, ttlEnabled bool) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - hash_key = "TestTableHashKey" - name = %[1]q - read_capacity = 1 - write_capacity = 1 - - attribute { - name = "TestTableHashKey" - type = "S" - } - - ttl { - attribute_name = %[2]q - enabled = %[3]t - } -} -`, rName, ttlAttribute, ttlEnabled) -} - func testAccTableConfig_TTL_missingAttributeName(rName string, ttlEnabled bool) string { return fmt.Sprintf(` resource "aws_dynamodb_table" "test" { From 919d0f3aa4226732ca12278dfc6239908d3edd5d Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 14 Jun 2024 17:11:45 -0700 Subject: [PATCH 4/7] Cleans up validation --- internal/service/dynamodb/table.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/internal/service/dynamodb/table.go b/internal/service/dynamodb/table.go index edbcb7bb1eac..c7e238e57272 100644 --- a/internal/service/dynamodb/table.go +++ b/internal/service/dynamodb/table.go @@ -2403,23 +2403,18 @@ func validateTTLCustomDiff(ctx context.Context, d *schema.ResourceDiff, meta any ttlPath := cty.GetAttrPath("ttl") ttl := configRaw.GetAttr("ttl") if ttl.IsKnown() && !ttl.IsNull() { - listenerActionsPlantimeValidate(ttlPath, ttl, &diags) + if ttl.LengthInt() == 1 { + idx := cty.NumberIntVal(0) + ttl := ttl.Index(idx) + ttlPath := ttlPath.Index(idx) + ttlPlantimeValidate(ttlPath, ttl, &diags) + } } return sdkdiag.DiagnosticsError(diags) } -func listenerActionsPlantimeValidate(ttlPath cty.Path, ttl cty.Value, diags *diag.Diagnostics) { - it := ttl.ElementIterator() - for it.Next() { - i, action := it.Element() - ttlPath := ttlPath.Index(i) - - listenerActionPlantimeValidate(ttlPath, action, diags) - } -} - -func listenerActionPlantimeValidate(ttlPath cty.Path, ttl cty.Value, diags *diag.Diagnostics) { +func ttlPlantimeValidate(ttlPath cty.Path, ttl cty.Value, diags *diag.Diagnostics) { attribute := ttl.GetAttr("attribute_name") if !attribute.IsKnown() { return From ea0697dc426ac78d408252977354950635d0c920 Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Fri, 14 Jun 2024 17:20:38 -0700 Subject: [PATCH 5/7] Uses `ConfigStateChecks` for `ttl` value checks --- internal/service/dynamodb/table_test.go | 57 +++++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index 1ed8833f5540..f5e96f12e07e 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -19,7 +19,10 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -372,10 +375,16 @@ func TestAccDynamoDBTable_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "table_class", "STANDARD"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsAllPercent, acctest.Ct0), - resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtFalse), resource.TestCheckResourceAttr(resourceName, "write_capacity", acctest.Ct1), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("ttl"), knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attribute_name": knownvalue.StringExact(""), + names.AttrEnabled: knownvalue.Bool(false), + }), + })), + }, }, { ResourceName: resourceName, @@ -1365,10 +1374,15 @@ func TestAccDynamoDBTable_TTL_enabled(t *testing.T) { Config: testAccTableConfig_timeToLive(rName, rName, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", rName), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtTrue), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("ttl"), knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attribute_name": knownvalue.StringExact(rName), + names.AttrEnabled: knownvalue.Bool(true), + }), + })), + }, }, { ResourceName: resourceName, @@ -1402,10 +1416,15 @@ func TestAccDynamoDBTable_TTL_disabled(t *testing.T) { Config: testAccTableConfig_timeToLive(rName, "", false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", ""), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtFalse), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("ttl"), knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attribute_name": knownvalue.StringExact(""), + names.AttrEnabled: knownvalue.Bool(false), + }), + })), + }, }, { ResourceName: resourceName, @@ -1439,10 +1458,15 @@ func TestAccDynamoDBTable_TTL_update(t *testing.T) { Config: testAccTableConfig_timeToLive(rName, "", false), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", ""), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtFalse), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("ttl"), knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attribute_name": knownvalue.StringExact(""), + names.AttrEnabled: knownvalue.Bool(false), + }), + })), + }, }, { ResourceName: resourceName, @@ -1453,10 +1477,15 @@ func TestAccDynamoDBTable_TTL_update(t *testing.T) { Config: testAccTableConfig_timeToLive(rName, rName, true), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckInitialTableExists(ctx, resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "ttl.0.attribute_name", rName), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", acctest.CtTrue), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("ttl"), knownvalue.ListExact([]knownvalue.Check{ + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attribute_name": knownvalue.StringExact(rName), + names.AttrEnabled: knownvalue.Bool(true), + }), + })), + }, }, }, }) From 744d5283a79828920207a2c35c47eaa2894f81cc Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 17 Jun 2024 10:08:58 -0700 Subject: [PATCH 6/7] Fixes formatting --- internal/service/dynamodb/table_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index f5e96f12e07e..9a73db6451a7 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -3648,7 +3648,7 @@ resource "aws_dynamodb_table" "test" { ttl { attribute_name = "" - enabled = %[2]t + enabled = %[2]t } } `, rName, ttlEnabled) From f6a9d3381f38f181219e1d3e818d67ad72777f7c Mon Sep 17 00:00:00 2001 From: Graham Davison Date: Mon, 17 Jun 2024 10:09:09 -0700 Subject: [PATCH 7/7] Adds CHANGELOG entry --- .changelog/37991.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/37991.txt diff --git a/.changelog/37991.txt b/.changelog/37991.txt new file mode 100644 index 000000000000..9b338f4e5f3f --- /dev/null +++ b/.changelog/37991.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_dynamodb_table: Fixes perpetual diff when `ttl.attribute_name` is set when `ttl.enabled` is not set. +``` + +```release-note:enhancement +resource/aws_dynamodb_table: Adds validation for `ttl` values. +```