Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamodb: New resource table replica #26250

Merged
merged 16 commits into from
Aug 11, 2022
3 changes: 3 additions & 0 deletions .changelog/26250.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_dynamodb_table_replica
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,7 @@ func New(_ context.Context) (*schema.Provider, error) {
"aws_dynamodb_kinesis_streaming_destination": dynamodb.ResourceKinesisStreamingDestination(),
"aws_dynamodb_table": dynamodb.ResourceTable(),
"aws_dynamodb_table_item": dynamodb.ResourceTableItem(),
"aws_dynamodb_table_replica": dynamodb.ResourceTableReplica(),
"aws_dynamodb_tag": dynamodb.ResourceTag(),

"aws_ami": ec2.ResourceAMI(),
Expand Down
20 changes: 20 additions & 0 deletions internal/service/dynamodb/arn.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package dynamodb

import (
"strings"

"github.com/aws/aws-sdk-go/aws/arn"
)

Expand All @@ -14,3 +16,21 @@ func ARNForNewRegion(rn string, newRegion string) (string, error) {

return parsedARN.String(), nil
}

func RegionFromARN(rn string) (string, error) {
parsedARN, err := arn.Parse(rn)
if err != nil {
return "", err
}

return parsedARN.Region, nil
}

func TableNameFromARN(rn string) (string, error) {
parsedARN, err := arn.Parse(rn)
if err != nil {
return "", err
}

return strings.TrimPrefix(parsedARN.Resource, "table/"), nil
}
112 changes: 65 additions & 47 deletions internal/service/dynamodb/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,21 @@ func ResourceTable() *schema.Resource {
},
},
},
"restore_date_time": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: verify.ValidUTCTimestamp,
},
"restore_source_name": {
Type: schema.TypeString,
Optional: true,
},
"restore_to_latest_time": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"server_side_encryption": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -324,6 +339,11 @@ func ResourceTable() *schema.Resource {
dynamodb.StreamViewTypeKeysOnly,
}, false),
},
"table_class": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(dynamodb.TableClass_Values(), false),
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
"ttl": {
Expand Down Expand Up @@ -351,26 +371,6 @@ func ResourceTable() *schema.Resource {
Computed: true,
Optional: true,
},
"table_class": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(dynamodb.TableClass_Values(), false),
},
"restore_source_name": {
Type: schema.TypeString,
Optional: true,
},
"restore_to_latest_time": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"restore_date_time": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: verify.ValidUTCTimestamp,
},
},
}
}
Expand All @@ -390,24 +390,23 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Creating DynamoDB table with key schema: %#v", keySchemaMap)

if _, ok := d.GetOk("restore_source_name"); ok {

req := &dynamodb.RestoreTableToPointInTimeInput{
input := &dynamodb.RestoreTableToPointInTimeInput{
TargetTableName: aws.String(d.Get("name").(string)),
SourceTableName: aws.String(d.Get("restore_source_name").(string)),
}

if v, ok := d.GetOk("restore_date_time"); ok {
t, _ := time.Parse(time.RFC3339, v.(string))
req.RestoreDateTime = aws.Time(t)
input.RestoreDateTime = aws.Time(t)
}

if attr, ok := d.GetOk("restore_to_latest_time"); ok {
req.UseLatestRestorableTime = aws.Bool(attr.(bool))
input.UseLatestRestorableTime = aws.Bool(attr.(bool))
}

if v, ok := d.GetOk("local_secondary_index"); ok {
lsiSet := v.(*schema.Set)
req.LocalSecondaryIndexOverride = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
input.LocalSecondaryIndexOverride = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
}

billingModeOverride := d.Get("billing_mode").(string)
Expand All @@ -418,13 +417,13 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
"write_capacity": d.Get("write_capacity"),
"read_capacity": d.Get("read_capacity"),
}
req.ProvisionedThroughputOverride = expandProvisionedThroughput(capacityMap, billingModeOverride)
input.ProvisionedThroughputOverride = expandProvisionedThroughput(capacityMap, billingModeOverride)
}
}

if v, ok := d.GetOk("local_secondary_index"); ok {
lsiSet := v.(*schema.Set)
req.LocalSecondaryIndexOverride = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
input.LocalSecondaryIndexOverride = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
}

if v, ok := d.GetOk("global_secondary_index"); ok {
Expand All @@ -440,17 +439,17 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
gsiObject := expandGlobalSecondaryIndex(gsi, billingModeOverride)
globalSecondaryIndexes = append(globalSecondaryIndexes, gsiObject)
}
req.GlobalSecondaryIndexOverride = globalSecondaryIndexes
input.GlobalSecondaryIndexOverride = globalSecondaryIndexes
}

if v, ok := d.GetOk("server_side_encryption"); ok {
req.SSESpecificationOverride = expandEncryptAtRestOptions(v.([]interface{}))
input.SSESpecificationOverride = expandEncryptAtRestOptions(v.([]interface{}))
}

var output *dynamodb.RestoreTableToPointInTimeOutput
err := resource.Retry(createTableTimeout, func() *resource.RetryError {
var err error
output, err = conn.RestoreTableToPointInTime(req)
output, err = conn.RestoreTableToPointInTime(input)
if err != nil {
if tfawserr.ErrCodeEquals(err, "ThrottlingException") {
return resource.RetryableError(err)
Expand All @@ -468,7 +467,7 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
})

if tfresource.TimedOut(err) {
output, err = conn.RestoreTableToPointInTime(req)
output, err = conn.RestoreTableToPointInTime(input)
}

if err != nil {
Expand All @@ -480,11 +479,14 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
}

} else {
req := &dynamodb.CreateTableInput{
input := &dynamodb.CreateTableInput{
TableName: aws.String(d.Get("name").(string)),
BillingMode: aws.String(d.Get("billing_mode").(string)),
KeySchema: expandKeySchema(keySchemaMap),
Tags: Tags(tags.IgnoreAWS()),
}

if len(tags) > 0 {
input.Tags = Tags(tags.IgnoreAWS())
}

billingMode := d.Get("billing_mode").(string)
Expand All @@ -494,16 +496,16 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
"read_capacity": d.Get("read_capacity"),
}

req.ProvisionedThroughput = expandProvisionedThroughput(capacityMap, billingMode)
input.ProvisionedThroughput = expandProvisionedThroughput(capacityMap, billingMode)

if v, ok := d.GetOk("attribute"); ok {
aSet := v.(*schema.Set)
req.AttributeDefinitions = expandAttributes(aSet.List())
input.AttributeDefinitions = expandAttributes(aSet.List())
}

if v, ok := d.GetOk("local_secondary_index"); ok {
lsiSet := v.(*schema.Set)
req.LocalSecondaryIndexes = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
input.LocalSecondaryIndexes = expandLocalSecondaryIndexes(lsiSet.List(), keySchemaMap)
}

if v, ok := d.GetOk("global_secondary_index"); ok {
Expand All @@ -519,28 +521,28 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
gsiObject := expandGlobalSecondaryIndex(gsi, billingMode)
globalSecondaryIndexes = append(globalSecondaryIndexes, gsiObject)
}
req.GlobalSecondaryIndexes = globalSecondaryIndexes
input.GlobalSecondaryIndexes = globalSecondaryIndexes
}

if v, ok := d.GetOk("stream_enabled"); ok {
req.StreamSpecification = &dynamodb.StreamSpecification{
input.StreamSpecification = &dynamodb.StreamSpecification{
StreamEnabled: aws.Bool(v.(bool)),
StreamViewType: aws.String(d.Get("stream_view_type").(string)),
}
}

if v, ok := d.GetOk("server_side_encryption"); ok {
req.SSESpecification = expandEncryptAtRestOptions(v.([]interface{}))
input.SSESpecification = expandEncryptAtRestOptions(v.([]interface{}))
}

if v, ok := d.GetOk("table_class"); ok {
req.TableClass = aws.String(v.(string))
input.TableClass = aws.String(v.(string))
}

var output *dynamodb.CreateTableOutput
err := resource.Retry(createTableTimeout, func() *resource.RetryError {
var err error
output, err = conn.CreateTable(req)
output, err = conn.CreateTable(input)
if err != nil {
if tfawserr.ErrCodeEquals(err, "ThrottlingException", "") {
return resource.RetryableError(err)
Expand All @@ -558,7 +560,7 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
})

if tfresource.TimedOut(err) {
output, err = conn.CreateTable(req)
output, err = conn.CreateTable(input)
}

if err != nil {
Expand All @@ -578,6 +580,18 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {
return create.Error(names.DynamoDB, create.ErrActionWaitingForCreation, ResNameTable, d.Id(), err)
}

if v, ok := d.GetOk("global_secondary_index"); ok {
gsiSet := v.(*schema.Set)

for _, gsiObject := range gsiSet.List() {
gsi := gsiObject.(map[string]interface{})

if _, err := waitGSIActive(conn, d.Id(), gsi["name"].(string), d.Timeout(schema.TimeoutUpdate)); err != nil {
return create.Error(names.DynamoDB, create.ErrActionWaitingForCreation, ResNameTable, d.Id(), fmt.Errorf("GSI (%s): %w", gsi["name"].(string), err))
}
}
}

if d.Get("ttl.0.enabled").(bool) {
if err := updateTimeToLive(conn, d.Id(), d.Get("ttl").([]interface{}), d.Timeout(schema.TimeoutCreate)); err != nil {
return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTable, d.Id(), fmt.Errorf("enabling TTL: %w", err))
Expand Down Expand Up @@ -605,8 +619,6 @@ func resourceTableCreate(d *schema.ResourceData, meta interface{}) error {

func resourceTableRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DynamoDBConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{
TableName: aws.String(d.Id()),
Expand Down Expand Up @@ -726,6 +738,9 @@ func resourceTableRead(d *schema.ResourceData, meta interface{}) error {
return create.SettingError(names.DynamoDB, ResNameTable, d.Id(), "ttl", err)
}

defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

tags, err := ListTags(conn, d.Get("arn").(string))

if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") {
Expand Down Expand Up @@ -947,7 +962,10 @@ func resourceTableDelete(d *schema.ResourceData, meta interface{}) error {

if replicas := d.Get("replica").(*schema.Set).List(); len(replicas) > 0 {
if err := deleteReplicas(conn, d.Id(), replicas, d.Timeout(schema.TimeoutDelete)); err != nil {
return create.Error(names.DynamoDB, create.ErrActionDeleting, ResNameTable, d.Id(), err)
// ValidationException: Replica specified in the Replica Update or Replica Delete action of the request was not found.
if !tfawserr.ErrMessageContains(err, "ValidationException", "request was not found") {
return create.Error(names.DynamoDB, create.ErrActionDeleting, ResNameTable, d.Id(), err)
}
}
}

Expand Down Expand Up @@ -1057,7 +1075,7 @@ func createReplicas(conn *dynamodb.DynamoDB, tableName string, tfList []interfac
return fmt.Errorf("creating replica (%s): %w", tfMap["region_name"].(string), err)
}

if _, err := waitReplicaActive(conn, tableName, tfMap["region_name"].(string), timeout); err != nil {
if err := waitReplicaActive(conn, tableName, tfMap["region_name"].(string), timeout); err != nil {
return fmt.Errorf("waiting for replica (%s) creation: %w", tfMap["region_name"].(string), err)
}

Expand Down Expand Up @@ -1416,7 +1434,7 @@ func deleteReplicas(conn *dynamodb.DynamoDB, tableName string, tfList []interfac
return fmt.Errorf("deleting replica (%s): %w", regionName, err)
}

if _, err := waitReplicaDeleted(conn, tableName, regionName, timeout); err != nil {
if err := waitReplicaDeleted(conn, tableName, regionName, timeout); err != nil {
return fmt.Errorf("waiting for replica (%s) deletion: %w", regionName, err)
}

Expand Down
Loading