Skip to content

Commit 85950dc

Browse files
authored
Merge pull request #27566 from hashicorp/b-dynamodb-table-stream-diffs
dynamodb/table: Fix stream diff bugs
2 parents 21e06c7 + 155ec67 commit 85950dc

File tree

3 files changed

+146
-4
lines changed

3 files changed

+146
-4
lines changed

.changelog/27566.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_dynamodb_table: Fix bug causing spurious diffs with and preventing proper updating of `stream_enabled` and `stream_view_type`
3+
```

internal/service/dynamodb/table.go

+53-2
Original file line numberDiff line numberDiff line change
@@ -684,8 +684,8 @@ func resourceTableRead(d *schema.ResourceData, meta interface{}) error {
684684
d.Set("stream_view_type", table.StreamSpecification.StreamViewType)
685685
d.Set("stream_enabled", table.StreamSpecification.StreamEnabled)
686686
} else {
687-
d.Set("stream_view_type", "")
688687
d.Set("stream_enabled", false)
688+
d.Set("stream_view_type", d.Get("stream_view_type").(string))
689689
}
690690

691691
d.Set("stream_arn", table.LatestStreamArn)
@@ -826,7 +826,21 @@ func resourceTableUpdate(d *schema.ResourceData, meta interface{}) error {
826826
input.ProvisionedThroughput = expandProvisionedThroughputUpdate(d.Id(), capacityMap, billingMode, oldBillingMode)
827827
}
828828

829-
if d.HasChanges("stream_enabled", "stream_view_type") {
829+
// make change when
830+
// stream_enabled has change (below) OR
831+
// stream_view_type has change and stream_enabled is true (special case)
832+
if !d.HasChange("stream_enabled") && d.HasChange("stream_view_type") {
833+
if v, ok := d.Get("stream_enabled").(bool); ok && v {
834+
// in order to change stream view type:
835+
// 1) stream have already been enabled, and
836+
// 2) it must be disabled and then reenabled (otherwise, ValidationException: Table already has an enabled stream)
837+
if err := cycleStreamEnabled(conn, d.Id(), d.Get("stream_view_type").(string), d.Timeout(schema.TimeoutUpdate)); err != nil {
838+
return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTable, d.Id(), err)
839+
}
840+
}
841+
}
842+
843+
if d.HasChange("stream_enabled") {
830844
hasTableUpdate = true
831845

832846
input.StreamSpecification = &dynamodb.StreamSpecification{
@@ -1003,6 +1017,43 @@ func isTableOptionDisabled(v interface{}) bool {
10031017

10041018
// CRUD helpers
10051019

1020+
// cycleStreamEnabled disables the stream and then re-enables it with streamViewType
1021+
func cycleStreamEnabled(conn *dynamodb.DynamoDB, id string, streamViewType string, timeout time.Duration) error {
1022+
input := &dynamodb.UpdateTableInput{
1023+
TableName: aws.String(id),
1024+
}
1025+
input.StreamSpecification = &dynamodb.StreamSpecification{
1026+
StreamEnabled: aws.Bool(false),
1027+
}
1028+
1029+
_, err := conn.UpdateTable(input)
1030+
1031+
if err != nil {
1032+
return fmt.Errorf("cycling stream enabled: %s", err)
1033+
}
1034+
1035+
if _, err := waitTableActive(conn, id, timeout); err != nil {
1036+
return fmt.Errorf("waiting for stream cycle: %s", err)
1037+
}
1038+
1039+
input.StreamSpecification = &dynamodb.StreamSpecification{
1040+
StreamEnabled: aws.Bool(true),
1041+
StreamViewType: aws.String(streamViewType),
1042+
}
1043+
1044+
_, err = conn.UpdateTable(input)
1045+
1046+
if err != nil {
1047+
return fmt.Errorf("cycling stream enabled: %s", err)
1048+
}
1049+
1050+
if _, err := waitTableActive(conn, id, timeout); err != nil {
1051+
return fmt.Errorf("waiting for stream cycle: %s", err)
1052+
}
1053+
1054+
return nil
1055+
}
1056+
10061057
func createReplicas(conn *dynamodb.DynamoDB, tableName string, tfList []interface{}, tfVersion string, create bool, timeout time.Duration) error {
10071058
for _, tfMapRaw := range tfList {
10081059
tfMap, ok := tfMapRaw.(map[string]interface{})

internal/service/dynamodb/table_test.go

+90-2
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,92 @@ func TestAccDynamoDBTable_streamSpecification(t *testing.T) {
788788
Check: resource.ComposeAggregateTestCheckFunc(
789789
testAccCheckInitialTableExists(resourceName, &conf),
790790
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"),
791-
resource.TestCheckResourceAttr(resourceName, "stream_view_type", ""),
791+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
792+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
793+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
794+
),
795+
},
796+
},
797+
})
798+
}
799+
800+
func TestAccDynamoDBTable_streamSpecificationDiffs(t *testing.T) {
801+
var conf dynamodb.DescribeTableOutput
802+
resourceName := "aws_dynamodb_table.test"
803+
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
804+
805+
resource.ParallelTest(t, resource.TestCase{
806+
PreCheck: func() { acctest.PreCheck(t) },
807+
ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID),
808+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
809+
CheckDestroy: testAccCheckTableDestroy,
810+
Steps: []resource.TestStep{
811+
{
812+
Config: testAccTableConfig_streamSpecification(rName, true, "KEYS_ONLY"),
813+
Check: resource.ComposeAggregateTestCheckFunc(
814+
testAccCheckInitialTableExists(resourceName, &conf),
815+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"),
816+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
817+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
818+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
819+
),
820+
},
821+
{
822+
Config: testAccTableConfig_streamSpecification(rName, true, "NEW_IMAGE"),
823+
Check: resource.ComposeAggregateTestCheckFunc(
824+
testAccCheckInitialTableExists(resourceName, &conf),
825+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"),
826+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "NEW_IMAGE"),
827+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
828+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
829+
),
830+
},
831+
{
832+
Config: testAccTableConfig_streamSpecification(rName, false, "NEW_IMAGE"),
833+
Check: resource.ComposeAggregateTestCheckFunc(
834+
testAccCheckInitialTableExists(resourceName, &conf),
835+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"),
836+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "NEW_IMAGE"),
837+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
838+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
839+
),
840+
},
841+
{
842+
Config: testAccTableConfig_streamSpecification(rName, false, "KEYS_ONLY"),
843+
Check: resource.ComposeAggregateTestCheckFunc(
844+
testAccCheckInitialTableExists(resourceName, &conf),
845+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"),
846+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
847+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
848+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
849+
),
850+
},
851+
{
852+
Config: testAccTableConfig_streamSpecification(rName, false, "null"),
853+
Check: resource.ComposeAggregateTestCheckFunc(
854+
testAccCheckInitialTableExists(resourceName, &conf),
855+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"),
856+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
857+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
858+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
859+
),
860+
},
861+
{
862+
Config: testAccTableConfig_streamSpecification(rName, true, "KEYS_ONLY"),
863+
Check: resource.ComposeAggregateTestCheckFunc(
864+
testAccCheckInitialTableExists(resourceName, &conf),
865+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"),
866+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
867+
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
868+
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
869+
),
870+
},
871+
{
872+
Config: testAccTableConfig_streamSpecification(rName, true, "KEYS_ONLY"),
873+
Check: resource.ComposeAggregateTestCheckFunc(
874+
testAccCheckInitialTableExists(resourceName, &conf),
875+
resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"),
876+
resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"),
792877
acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))),
793878
resource.TestCheckResourceAttrSet(resourceName, "stream_label"),
794879
),
@@ -2442,6 +2527,9 @@ resource "aws_dynamodb_table" "test" {
24422527
}
24432528

24442529
func testAccTableConfig_streamSpecification(rName string, enabled bool, viewType string) string {
2530+
if viewType != "null" {
2531+
viewType = fmt.Sprintf(`"%s"`, viewType)
2532+
}
24452533
return fmt.Sprintf(`
24462534
resource "aws_dynamodb_table" "test" {
24472535
name = %[1]q
@@ -2455,7 +2543,7 @@ resource "aws_dynamodb_table" "test" {
24552543
}
24562544
24572545
stream_enabled = %[2]t
2458-
stream_view_type = %[3]q
2546+
stream_view_type = %[3]s
24592547
}
24602548
`, rName, enabled, viewType)
24612549
}

0 commit comments

Comments
 (0)