@@ -226,6 +226,22 @@ func resourceTable() *schema.Resource {
226226 },
227227 },
228228 },
229+ "global_table_witness" : {
230+ Type : schema .TypeList ,
231+ Optional : true ,
232+ Computed : true ,
233+ MaxItems : 1 ,
234+ Elem : & schema.Resource {
235+ Schema : map [string ]* schema.Schema {
236+ "region_name" : {
237+ Type : schema .TypeString ,
238+ Optional : true ,
239+ Computed : true ,
240+ ValidateFunc : verify .ValidRegionName ,
241+ },
242+ },
243+ },
244+ },
229245 "hash_key" : {
230246 Type : schema .TypeString ,
231247 Optional : true ,
@@ -884,7 +900,7 @@ func resourceTableCreate(ctx context.Context, d *schema.ResourceData, meta any)
884900 }
885901
886902 if v := d .Get ("replica" ).(* schema.Set ); v .Len () > 0 {
887- if err := createReplicas (ctx , conn , d .Id (), v .List (), true , d .Timeout (schema .TimeoutCreate )); err != nil {
903+ if err := createReplicas (ctx , conn , d .Id (), v .List (), expandGlobalTableWitness ( d . Get ( "global_table_witness" )), true , d .Timeout (schema .TimeoutCreate )); err != nil {
888904 return create .AppendDiagError (diags , names .DynamoDB , create .ErrActionCreating , resNameTable , d .Id (), fmt .Errorf ("replicas: %w" , err ))
889905 }
890906
@@ -951,6 +967,10 @@ func resourceTableRead(ctx context.Context, d *schema.ResourceData, meta any) di
951967 return create .AppendDiagSettingError (diags , names .DynamoDB , resNameTable , d .Id (), "global_secondary_index" , err )
952968 }
953969
970+ if err := d .Set ("global_table_witness" , flattenGlobalTableWitnesses (table .GlobalTableWitnesses )); err != nil {
971+ return create .AppendDiagSettingError (diags , names .DynamoDB , resNameTable , d .Id (), "global_table_witness" , err )
972+ }
973+
954974 if err := d .Set ("on_demand_throughput" , flattenOnDemandThroughput (table .OnDemandThroughput )); err != nil {
955975 return create .AppendDiagSettingError (diags , names .DynamoDB , resNameTable , d .Id (), "on_demand_throughput" , err )
956976 }
@@ -1393,10 +1413,11 @@ func resourceTableDelete(ctx context.Context, d *schema.ResourceData, meta any)
13931413
13941414 if replicas := d .Get ("replica" ).(* schema.Set ).List (); len (replicas ) > 0 {
13951415 log .Printf ("[DEBUG] Deleting DynamoDB Table replicas: %s" , d .Id ())
1396- if err := deleteReplicas (ctx , conn , d .Id (), replicas , d .Timeout (schema .TimeoutDelete )); err != nil {
1416+ if err := deleteReplicas (ctx , conn , d .Id (), replicas , expandGlobalTableWitness ( d . Get ( "global_table_witness" )), d .Timeout (schema .TimeoutDelete )); err != nil {
13971417 // ValidationException: Replica specified in the Replica Update or Replica Delete action of the request was not found.
13981418 // ValidationException: Cannot add, delete, or update the local region through ReplicaUpdates. Use CreateTable, DeleteTable, or UpdateTable as required.
13991419 if ! tfawserr .ErrMessageContains (err , errCodeValidationException , "request was not found" ) &&
1420+ ! tfawserr .ErrMessageContains (err , errCodeValidationException , "MultiRegionConsistency must be set as STRONG when GlobalTableWitnessUpdates parameter is present" ) &&
14001421 ! tfawserr .ErrMessageContains (err , errCodeValidationException , "Cannot add, delete, or update the local region through ReplicaUpdates" ) {
14011422 return create .AppendDiagError (diags , names .DynamoDB , create .ErrActionDeleting , resNameTable , d .Id (), err )
14021423 }
@@ -1471,7 +1492,7 @@ func cycleStreamEnabled(ctx context.Context, conn *dynamodb.Client, id string, s
14711492 return nil
14721493}
14731494
1474- func createReplicas (ctx context.Context , conn * dynamodb.Client , tableName string , tfList []any , create bool , timeout time.Duration ) error {
1495+ func createReplicas (ctx context.Context , conn * dynamodb.Client , tableName string , tfList []any , globalTableWitnessRegionName string , create bool , timeout time.Duration ) error {
14751496 // Duplicating this for MRSC Adoption. If using MRSC and CreateReplicationGroupMemberAction list isn't initiated for at least 2 replicas
14761497 // then the update table action will fail with
14771498 // "Unsupported table replica count for global tables with MultiRegionConsistency set to STRONG"
@@ -1492,14 +1513,18 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
14921513 }
14931514
14941515 if numReplicasMRSC > 0 {
1516+ mrscErrorMsg := "creating replicas: Using MultiRegionStrongConsistency requires exactly 2 replicas, or 1 replica and 1 witness region."
14951517 if numReplicasMRSC > 0 && numReplicasMRSC != numReplicas {
1496- return fmt .Errorf ("creating replicas: Using MultiRegionStrongConsistency requires all replicas to use 'consistency_mode' set to 'STRONG' " )
1518+ return errors .New (mrscErrorMsg )
1519+ }
1520+ if numReplicasMRSC == 1 && globalTableWitnessRegionName == "" {
1521+ return fmt .Errorf ("%s Only MRSC Replica count of 1 was provided but no Witness region was provided" , mrscErrorMsg )
14971522 }
1498- if numReplicasMRSC == 1 {
1499- return fmt .Errorf ("creating replicas: Using MultiRegionStrongConsistency requires exactly 2 replicas. " )
1523+ if numReplicasMRSC == 2 && ( numReplicasMRSC == numReplicas && globalTableWitnessRegionName != "" ) {
1524+ return fmt .Errorf ("%s MRSC Replica count of 2 was provided and a Witness region was also provided" , mrscErrorMsg )
15001525 }
15011526 if numReplicasMRSC > 2 {
1502- return fmt .Errorf ("creating replicas: Using MultiRegionStrongConsistency supports at most 2 replicas. " )
1527+ return fmt .Errorf ("%s Too many Replicas were provided %d" , mrscErrorMsg , numReplicasMRSC )
15031528 }
15041529
15051530 mrscInput = awstypes .MultiRegionConsistencyStrong
@@ -1530,14 +1555,24 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
15301555 })
15311556 }
15321557
1533- input := & dynamodb.UpdateTableInput {
1534- TableName : aws .String (tableName ),
1535- ReplicaUpdates : replicaCreates ,
1536- MultiRegionConsistency : mrscInput ,
1558+ var gtgwu []awstypes.GlobalTableWitnessGroupUpdate
1559+ if globalTableWitnessRegionName != "" {
1560+ var cgtwgma = awstypes.CreateGlobalTableWitnessGroupMemberAction {
1561+ RegionName : aws .String (globalTableWitnessRegionName ),
1562+ }
1563+ gtgwu = append (gtgwu , awstypes.GlobalTableWitnessGroupUpdate {
1564+ Create : & cgtwgma ,
1565+ })
1566+ }
1567+ input := dynamodb.UpdateTableInput {
1568+ GlobalTableWitnessUpdates : gtgwu ,
1569+ MultiRegionConsistency : mrscInput ,
1570+ ReplicaUpdates : replicaCreates ,
1571+ TableName : aws .String (tableName ),
15371572 }
15381573
15391574 err := tfresource .Retry (ctx , max (replicaUpdateTimeout , timeout ), func (ctx context.Context ) * tfresource.RetryError {
1540- _ , err := conn .UpdateTable (ctx , input )
1575+ _ , err := conn .UpdateTable (ctx , & input )
15411576 if err != nil {
15421577 if tfawserr .ErrCodeEquals (err , errCodeThrottlingException ) {
15431578 return tfresource .RetryableError (err )
@@ -1655,7 +1690,7 @@ func createReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
16551690 // ValidationException: One or more parameter values were invalid: KMSMasterKeyId must be specified for each replica.
16561691
16571692 if create && tfawserr .ErrMessageContains (err , errCodeValidationException , "already exist" ) {
1658- return createReplicas (ctx , conn , tableName , tfList , false , timeout )
1693+ return createReplicas (ctx , conn , tableName , tfList , globalTableWitnessRegionName , false , timeout )
16591694 }
16601695
16611696 if err != nil && ! tfawserr .ErrMessageContains (err , errCodeValidationException , "no actions specified" ) {
@@ -1898,20 +1933,22 @@ func updateReplica(ctx context.Context, conn *dynamodb.Client, d *schema.Resourc
18981933 }
18991934 }
19001935
1936+ globalTableWitnessRegionName := expandGlobalTableWitness (d .Get ("global_table_witness" ))
1937+
19011938 if len (removeFirst ) > 0 { // mini ForceNew, recreates replica but doesn't recreate the table
1902- if err := deleteReplicas (ctx , conn , d .Id (), removeFirst , d .Timeout (schema .TimeoutUpdate )); err != nil {
1939+ if err := deleteReplicas (ctx , conn , d .Id (), removeFirst , globalTableWitnessRegionName , d .Timeout (schema .TimeoutUpdate )); err != nil {
19031940 return fmt .Errorf ("updating replicas, while deleting: %w" , err )
19041941 }
19051942 }
19061943
19071944 if len (toRemove ) > 0 {
1908- if err := deleteReplicas (ctx , conn , d .Id (), toRemove , d .Timeout (schema .TimeoutUpdate )); err != nil {
1945+ if err := deleteReplicas (ctx , conn , d .Id (), toRemove , globalTableWitnessRegionName , d .Timeout (schema .TimeoutUpdate )); err != nil {
19091946 return fmt .Errorf ("updating replicas, while deleting: %w" , err )
19101947 }
19111948 }
19121949
19131950 if len (toAdd ) > 0 {
1914- if err := createReplicas (ctx , conn , d .Id (), toAdd , true , d .Timeout (schema .TimeoutCreate )); err != nil {
1951+ if err := createReplicas (ctx , conn , d .Id (), toAdd , globalTableWitnessRegionName , true , d .Timeout (schema .TimeoutCreate )); err != nil {
19151952 return fmt .Errorf ("updating replicas, while creating: %w" , err )
19161953 }
19171954 }
@@ -2164,7 +2201,7 @@ func deleteTable(ctx context.Context, conn *dynamodb.Client, tableName string) e
21642201 return err
21652202}
21662203
2167- func deleteReplicas (ctx context.Context , conn * dynamodb.Client , tableName string , tfList []any , timeout time.Duration ) error {
2204+ func deleteReplicas (ctx context.Context , conn * dynamodb.Client , tableName string , tfList []any , globalTableWitnessRegionName string , timeout time.Duration ) error {
21682205 var g multierror.Group
21692206
21702207 var replicaDeletes []awstypes.ReplicationGroupUpdate
@@ -2199,12 +2236,22 @@ func deleteReplicas(ctx context.Context, conn *dynamodb.Client, tableName string
21992236 // We built an array of MultiRegionStrongConsistency replicas that need deletion.
22002237 // These need to all happen concurrently
22012238 if len (replicaDeletes ) > 0 {
2202- input := & dynamodb.UpdateTableInput {
2203- TableName : aws .String (tableName ),
2204- ReplicaUpdates : replicaDeletes ,
2239+ var witnessDeletes []awstypes.GlobalTableWitnessGroupUpdate
2240+ if globalTableWitnessRegionName != "" {
2241+ witnessDeletes = append (witnessDeletes , awstypes.GlobalTableWitnessGroupUpdate {
2242+ Delete : & awstypes.DeleteGlobalTableWitnessGroupMemberAction {
2243+ RegionName : aws .String (globalTableWitnessRegionName ),
2244+ },
2245+ })
2246+ }
2247+
2248+ input := dynamodb.UpdateTableInput {
2249+ GlobalTableWitnessUpdates : witnessDeletes ,
2250+ ReplicaUpdates : replicaDeletes ,
2251+ TableName : aws .String (tableName ),
22052252 }
22062253 err := tfresource .Retry (ctx , updateTableTimeout , func (ctx context.Context ) * tfresource.RetryError {
2207- _ , err := conn .UpdateTable (ctx , input )
2254+ _ , err := conn .UpdateTable (ctx , & input )
22082255 notFoundRetries := 0
22092256 if err != nil {
22102257 if tfawserr .ErrCodeEquals (err , errCodeThrottlingException ) {
@@ -2694,6 +2741,24 @@ func flattenGSIWarmThroughput(apiObject *awstypes.GlobalSecondaryIndexWarmThroug
26942741 return []any {m }
26952742}
26962743
2744+ func expandGlobalTableWitness (v any ) string {
2745+ if v == nil || len (v .([]any )) == 0 || v .([]any )[0 ] == nil {
2746+ return ""
2747+ }
2748+
2749+ return v .([]any )[0 ].(map [string ]any )["region_name" ].(string )
2750+ }
2751+
2752+ func flattenGlobalTableWitnesses (apiObjects []awstypes.GlobalTableWitnessDescription ) []any {
2753+ if len (apiObjects ) != 1 {
2754+ return []any {}
2755+ }
2756+
2757+ return []any {map [string ]any {
2758+ "region_name" : aws .ToString (apiObjects [0 ].RegionName ),
2759+ }}
2760+ }
2761+
26972762func flattenReplicaDescription (apiObject * awstypes.ReplicaDescription ) map [string ]any {
26982763 if apiObject == nil {
26992764 return nil
0 commit comments