From e1c0df35b73153bf4812204e6240d6e942551946 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 9 Aug 2022 17:21:47 -0400 Subject: [PATCH 01/15] Add new resource table replica --- internal/service/dynamodb/arn.go | 20 + internal/service/dynamodb/table.go | 110 +- internal/service/dynamodb/table_replica.go | 653 ++++ .../service/dynamodb/table_replica_test.go | 2868 +++++++++++++++++ internal/service/dynamodb/table_test.go | 68 + 5 files changed, 3694 insertions(+), 25 deletions(-) create mode 100644 internal/service/dynamodb/table_replica.go create mode 100644 internal/service/dynamodb/table_replica_test.go diff --git a/internal/service/dynamodb/arn.go b/internal/service/dynamodb/arn.go index 24b68217ccd..8aedf611f50 100644 --- a/internal/service/dynamodb/arn.go +++ b/internal/service/dynamodb/arn.go @@ -1,6 +1,8 @@ package dynamodb import ( + "strings" + "github.com/aws/aws-sdk-go/aws/arn" ) @@ -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 +} diff --git a/internal/service/dynamodb/table.go b/internal/service/dynamodb/table.go index f1abb402c5b..b14b4af5f37 100644 --- a/internal/service/dynamodb/table.go +++ b/internal/service/dynamodb/table.go @@ -90,6 +90,7 @@ func ResourceTable() *schema.Resource { // https://github.com/hashicorp/terraform-provider-aws/issues/25214 return old.(string) != new.(string) && new.(string) != "" }), + setReplicaTagsDiff, verify.SetTagsDiff, ), @@ -270,6 +271,8 @@ func ResourceTable() *schema.Resource { Type: schema.TypeString, Required: true, }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), }, }, }, @@ -387,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) @@ -415,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 { @@ -437,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) @@ -465,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 { @@ -477,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) @@ -491,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 { @@ -516,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) @@ -555,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 { @@ -602,8 +607,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()), @@ -723,6 +726,9 @@ func resourceTableRead(d *schema.ResourceData, meta interface{}) error { return names.ErrorSetting(names.DynamoDB, "Table", 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.") { @@ -965,6 +971,53 @@ func resourceTableDelete(d *schema.ResourceData, meta interface{}) error { // custom diff +func setReplicaTagsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + resourceTags := tftags.New(diff.Get("tags").(map[string]interface{})) + + allTags := defaultTagsConfig.MergeTags(resourceTags).IgnoreConfig(ignoreTagsConfig) + + replicas := diff.Get("replica").(*schema.Set).List() + + setNew := false + tagsAllExist := false + + for i, replicaRaw := range replicas { + replica := replicaRaw.(map[string]interface{}) + + replicaAllTags := tftags.New(replica["tags"].(map[string]interface{})) + + if replica["propagate_tags"].(bool) { + replicaAllTags.Merge(allTags) + } + + if len(replica["tags_all"].(map[string]interface{})) > 0 { + tagsAllExist = true + } + + if len(replicaAllTags) > 0 { + replica["tags_all"] = replicaAllTags.Map() + setNew = true + } + + replicas[i] = replica + } + + if setNew { + if err := diff.SetNew("replica", replicas); err != nil { + return fmt.Errorf("setting new replica tags_all diff: %w", err) + } + } else if tagsAllExist { + if err := diff.SetNewComputed("replica"); err != nil { + return fmt.Errorf("setting replica tags_all to computed: %w", err) + } + } + + return nil +} + func isTableOptionDisabled(v interface{}) bool { options := v.([]interface{}) if len(options) == 0 { @@ -1081,7 +1134,14 @@ func updateReplicaTags(conn *dynamodb.DynamoDB, rn string, replicas []interface{ continue } + replicaTags := tftags.New(tfMap["tags"].(map[string]interface{})) + allTags := replicaTags + if v, ok := tfMap["propagate_tags"].(bool); ok && v { + allTags = replicaTags.Merge(tftags.New(newTags)) + } + + if len(allTags) > 0 { if aws.StringValue(conn.Config.Region) != region { session, err := conns.NewSessionForRegion(&conn.Config, region, terraformVersion) if err != nil { @@ -1096,7 +1156,7 @@ func updateReplicaTags(conn *dynamodb.DynamoDB, rn string, replicas []interface{ return fmt.Errorf("per region ARN for replica (%s): %w", region, err) } - if err := UpdateTags(conn, newARN, oldTags, newTags); err != nil { + if err := UpdateTags(conn, newARN, oldTags, allTags); err != nil { return fmt.Errorf("updating tags: %w", err) } } diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go new file mode 100644 index 00000000000..a0ba586e54d --- /dev/null +++ b/internal/service/dynamodb/table_replica.go @@ -0,0 +1,653 @@ +package dynamodb + +import ( + "errors" + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func ResourceTableReplica() *schema.Resource { + //lintignore:R011 + return &schema.Resource{ + Create: resourceTableReplicaCreate, + Read: resourceTableReplicaRead, + Update: resourceTableReplicaUpdate, + Delete: resourceTableReplicaDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(createTableTimeout), + Delete: schema.DefaultTimeout(deleteTableTimeout), + Update: schema.DefaultTimeout(updateTableTimeoutTotal), + }, + + CustomizeDiff: customdiff.All( + verify.SetTagsDiff, + ), + + Schema: map[string]*schema.Schema{ + "arn": { // direct to replica + Type: schema.TypeString, + Computed: true, + }, + "global_secondary_index": { // through global table + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "read_capacity_override": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "global_table_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "kms_key_arn": { // through global table + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: verify.ValidARN, + }, + "point_in_time_recovery": { // direct to replica + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "propagate_tags": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "read_capacity_override": { // through global table + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "table_class_override": { // through global table + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice(dynamodb.TableClass_Values(), false), + }, + "tags": tftags.TagsSchema(), // direct to replica + "tags_all": tftags.TagsSchemaComputed(), // direct to replica + }, + } +} + +func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).DynamoDBConn + + globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + } + + if globalRegion == aws.StringValue(conn.Config.Region) { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as global table")) + } + + session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + } + + conn = dynamodb.New(session) // now global table region + + var replicaInput = &dynamodb.CreateReplicationGroupMemberAction{} + + replicaInput.RegionName = conn.Config.Region + + if v, ok := d.GetOk("kms_key_arn"); ok { + replicaInput.KMSMasterKeyId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("global_secondary_index"); ok && len(v.([]interface{})) > 0 { + replicaInput.GlobalSecondaryIndexes = expandReplicaGlobalSecondaryIndexes(v.([]interface{})) + } + + if v, ok := d.GetOk("read_capacity_override"); ok { + replicaInput.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ + ReadCapacityUnits: aws.Int64(v.(int64)), + } + } + + if v, ok := d.GetOk("table_class_override"); ok { + replicaInput.TableClassOverride = aws.String(v.(string)) + } + + tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return fmt.Errorf("creating replica of %s: %w", d.Get("global_table_arn").(string), err) + } + + input := &dynamodb.UpdateTableInput{ + TableName: aws.String(tableName), + ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{ + { + Create: replicaInput, + }, + }, + } + + err = resource.Retry(maxDuration(replicaUpdateTimeout, d.Timeout(schema.TimeoutCreate)), func() *resource.RetryError { + _, err := conn.UpdateTable(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, "ThrottlingException") { + return resource.RetryableError(err) + } + if tfawserr.ErrMessageContains(err, dynamodb.ErrCodeLimitExceededException, "simultaneously") { + return resource.RetryableError(err) + } + if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceInUseException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + return nil + }) + + if tfresource.TimedOut(err) { + _, err = conn.UpdateTable(input) + } + + if err != nil { + return fmt.Errorf("creating replica (%s): %w", d.Get("global_table_arn").(string), err) + } + + if _, err := waitReplicaActive(conn, tableName, meta.(*conns.AWSClient).Region, d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("waiting for replica (%s) creation: %w", meta.(*conns.AWSClient).Region, err) + } + + repARN, err := ARNForNewRegion(d.Get("global_table_arn").(string), aws.StringValue(conn.Config.Region)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + } + d.SetId(repARN) + + return resourceTableReplicaRead(d, meta) +} + +func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { + // handled through global table (main) + // * global_secondary_index + // * kms_key_arn + // * read_capacity_override + // * table_class_override + conn := meta.(*conns.AWSClient).DynamoDBConn + + replicaRegion := aws.StringValue(conn.Config.Region) + + globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + } + + if globalRegion == replicaRegion { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) + } + + session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + } + + conn = dynamodb.New(session) // now global table region + + tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return fmt.Errorf("reading replica (%s): %w", d.Id(), err) + } + + result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{ + TableName: aws.String(tableName), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Dynamodb Table Replica (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), err) + } + + if result == nil || result.Table == nil { + if d.IsNewResource() { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + } + names.LogNotFoundRemoveState(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id()) + d.SetId("") + return nil + } + + replica, err := filterReplicasByRegion(result.Table.Replicas, replicaRegion) + + if err := d.Set("global_secondary_index", flattenReplicaGlobalSecondaryIndexes(replica.GlobalSecondaryIndexes)); err != nil { + return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "global_secondary_index", err) + } + + d.Set("kms_key_arn", replica.KMSMasterKeyId) + + if replica.ProvisionedThroughputOverride != nil { + d.Set("read_capacity_override", replica.ProvisionedThroughputOverride.ReadCapacityUnits) + } else { + d.Set("read_capacity_override", nil) + } + + if replica.ReplicaTableClassSummary != nil { + d.Set("table_class_override", replica.ReplicaTableClassSummary.TableClass) + } else { + d.Set("table_class_override", nil) + } + + return resourceTableReplicaReadReplica(d, meta) +} + +func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) error { + // handled direct to replica + // * arn + // * point_in_time_recovery + // * tags + conn := meta.(*conns.AWSClient).DynamoDBConn + + tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return fmt.Errorf("reading replica (%s): %w", d.Id(), err) + } + + result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{ + TableName: aws.String(tableName), + }) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] Dynamodb Table Replica (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), err) + } + + if result == nil || result.Table == nil { + if d.IsNewResource() { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + } + names.LogNotFoundRemoveState(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id()) + d.SetId("") + return nil + } + + d.Set("arn", result.Table.TableArn) + + pitrOut, err := conn.DescribeContinuousBackups(&dynamodb.DescribeContinuousBackupsInput{ + TableName: aws.String(d.Id()), + }) + + if err != nil && !tfawserr.ErrCodeEquals(err, "UnknownOperationException") { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table", d.Id(), fmt.Errorf("continuous backups: %w", err)) + } + + if pitrOut != nil && pitrOut.ContinuousBackupsDescription != nil && pitrOut.ContinuousBackupsDescription.ContinuousBackupsStatus != nil { + d.Set("point_in_time_recovery", aws.StringValue(pitrOut.ContinuousBackupsDescription.ContinuousBackupsStatus) == dynamodb.PointInTimeRecoveryStatusEnabled) + } else { + d.Set("point_in_time_recovery", false) + } + + 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.") { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "tags", err) + } + + if d.Get("propagate_tags").(bool) { + globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + } + + session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + } + + conn = dynamodb.New(session) // now global table region + + globalTags, err := ListTags(conn, d.Get("global_table_arn").(string)) + + if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { + return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) + } + + globalTags = globalTags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + globalTags = globalTags.RemoveDefaultConfig(defaultTagsConfig) + + tags = tags.Merge(globalTags) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "tags_all", err) + } + + return nil +} + +func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error { + // handled through global table (main) + // * global_secondary_index + // * kms_key_arn + // * read_capacity_override + // * table_class_override + conn := meta.(*conns.AWSClient).DynamoDBConn + + tableName, err := TableNameFromARN(d.Id()) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + } + + replicaRegion := aws.StringValue(conn.Config.Region) + + globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + } + + if globalRegion == replicaRegion { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) + } + + session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + } + + conn = dynamodb.New(session) // now global table region + + viaGlobalChanges := false + viaGlobalInput := &dynamodb.UpdateReplicationGroupMemberAction{ + RegionName: aws.String(replicaRegion), + } + + if d.HasChange("global_secondary_index") { + viaGlobalChanges = true + viaGlobalInput.GlobalSecondaryIndexes = expandReplicaGlobalSecondaryIndexes(d.Get("global_secondary_index").(*schema.Set).List()) + } + + if d.HasChange("kms_key_arn") { + viaGlobalChanges = true + viaGlobalInput.KMSMasterKeyId = aws.String(d.Get("kms_key_arn").(string)) + } + + if d.HasChange("read_capacity_override") { + viaGlobalChanges = true + viaGlobalInput.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ + ReadCapacityUnits: aws.Int64(d.Get("read_capacity_override").(int64)), + } + } + + if d.HasChange("table_class_override") { + viaGlobalChanges = true + viaGlobalInput.TableClassOverride = aws.String(d.Get("table_class_override").(string)) + } + + if viaGlobalChanges { + input := &dynamodb.UpdateTableInput{ + ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{ + { + Update: viaGlobalInput, + }, + }, + TableName: aws.String(tableName), + } + + err := resource.Retry(maxDuration(replicaUpdateTimeout, d.Timeout(schema.TimeoutUpdate)), func() *resource.RetryError { + _, err := conn.UpdateTable(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, "ThrottlingException") { + return resource.RetryableError(err) + } + if tfawserr.ErrMessageContains(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") { + return resource.RetryableError(err) + } + if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceInUseException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + return nil + }) + + if tfresource.TimedOut(err) { + _, err = conn.UpdateTable(input) + } + + if err != nil && !tfawserr.ErrMessageContains(err, "ValidationException", "no actions specified") { + return fmt.Errorf("creating replica (%s): %w", d.Id(), err) + } + + if _, err := waitReplicaActive(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("waiting for replica (%s) creation: %w", d.Id(), err) + } + } + + // handled direct to replica + // * point_in_time_recovery + // * tags + conn = meta.(*conns.AWSClient).DynamoDBConn + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + } + } + + if d.HasChange("point_in_time_recovery") { + if err := updatePITR(conn, tableName, d.Get("point_in_time_recovery").(bool), aws.StringValue(conn.Config.Region), meta.(*conns.AWSClient).TerraformVersion, d.Timeout(schema.TimeoutUpdate)); err != nil { + return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + } + } + + return resourceTableReplicaRead(d, meta) +} + +func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).DynamoDBConn + + log.Printf("[DEBUG] DynamoDB delete Table Replica: %s", d.Id()) + + tableName, err := TableNameFromARN(d.Id()) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + } + + replicaRegion := aws.StringValue(conn.Config.Region) + + globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + } + + session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + } + + conn = dynamodb.New(session) // now global table region + + input := &dynamodb.UpdateTableInput{ + TableName: aws.String(tableName), + ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{ + { + Delete: &dynamodb.DeleteReplicationGroupMemberAction{ + RegionName: aws.String(replicaRegion), + }, + }, + }, + } + + err = resource.Retry(updateTableTimeout, func() *resource.RetryError { + _, err := conn.UpdateTable(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, "ThrottlingException") { + return resource.RetryableError(err) + } + if tfawserr.ErrMessageContains(err, dynamodb.ErrCodeLimitExceededException, "can be created, updated, or deleted simultaneously") { + return resource.RetryableError(err) + } + if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceInUseException) { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + return nil + }) + + if tfresource.TimedOut(err) { + _, err = conn.UpdateTable(input) + } + + if err != nil { + return fmt.Errorf("deleting replica (%s): %w", replicaRegion, err) + } + + if _, err := waitReplicaDeleted(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("waiting for replica (%s) deletion: %w", replicaRegion, err) + } + + return nil +} + +func filterReplicasByRegion(replicas []*dynamodb.ReplicaDescription, region string) (*dynamodb.ReplicaDescription, error) { + if len(replicas) == 0 { + return nil, errors.New("no replicas found") + } + + for _, replica := range replicas { + if aws.StringValue(replica.RegionName) == region { + return replica, nil + } + } + + return nil, errors.New("replica not found") +} + +func expandReplicaGlobalSecondaryIndex(data map[string]interface{}) *dynamodb.ReplicaGlobalSecondaryIndex { + if data == nil { + return nil + } + + idx := &dynamodb.ReplicaGlobalSecondaryIndex{ + IndexName: aws.String(data["name"].(string)), + } + + if v, ok := data["read_capacity_override"].(int64); ok { + idx.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ + ReadCapacityUnits: aws.Int64(v), + } + } + + return idx +} + +func expandReplicaGlobalSecondaryIndexes(tfList []interface{}) []*dynamodb.ReplicaGlobalSecondaryIndex { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*dynamodb.ReplicaGlobalSecondaryIndex + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandReplicaGlobalSecondaryIndex(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenReplicaGlobalSecondaryIndex(apiObject *dynamodb.ReplicaGlobalSecondaryIndexDescription) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if apiObject.IndexName != nil { + tfMap["name"] = aws.StringValue(apiObject.IndexName) + } + + if apiObject.ProvisionedThroughputOverride != nil && apiObject.ProvisionedThroughputOverride.ReadCapacityUnits != nil { + tfMap["read_capacity_override"] = aws.Int64Value(apiObject.ProvisionedThroughputOverride.ReadCapacityUnits) + } + + return tfMap +} + +func flattenReplicaGlobalSecondaryIndexes(apiObjects []*dynamodb.ReplicaGlobalSecondaryIndexDescription) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenReplicaGlobalSecondaryIndex(apiObject)) + } + + return tfList +} diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go new file mode 100644 index 00000000000..b246a7b1089 --- /dev/null +++ b/internal/service/dynamodb/table_replica_test.go @@ -0,0 +1,2868 @@ +package dynamodb_test + +import ( + "errors" + "fmt" + "log" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfdynamodb "github.com/hashicorp/terraform-provider-aws/internal/service/dynamodb" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccDynamoDBTableReplica_basic(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "dynamodb", fmt.Sprintf("table/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "hash_key", rName), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": rName, + "type": "S", + }), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "table_class", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_disappears(t *testing.T) { + var table1 dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table1), + acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTable(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Disappears_payPerRequestWithGSI(t *testing.T) { + var table1, table2 dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table1), + acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTable(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_extended(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_initialState(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + testAccCheckInitialTableReplicaConf(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_addSecondaryGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "hash_key", "TestTableHashKey"), + resource.TestCheckResourceAttr(resourceName, "range_key", "TestTableRangeKey"), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "2"), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "2"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), + resource.TestCheckResourceAttr(resourceName, "attribute.#", "4"), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "1"), + resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestTableHashKey", + "type": "S", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestTableRangeKey", + "type": "S", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestLSIRangeKey", + "type": "N", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "ReplacementGSIRangeKey", + "type": "N", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "name": "ReplacementTestTableGSI", + "hash_key": "TestTableHashKey", + "range_key": "ReplacementGSIRangeKey", + "write_capacity": "5", + "read_capacity": "5", + "projection_type": "INCLUDE", + "non_key_attributes.#": "1", + }), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "TestNonKeyAttribute"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ + "name": "TestTableLSI", + "range_key": "TestLSIRangeKey", + "projection_type": "ALL", + }), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_enablePITR(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_initialState(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + testAccCheckInitialTableReplicaConf(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_backup(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "true"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingMode_payPerRequestToProvisioned(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingPayPerRequest(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingProvisioned(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingMode_payPerRequestToProvisionedIgnoreChanges(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingPayPerRequest(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingProvisionedIgnoreChanges(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "1"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingMode_provisionedToPayPerRequest(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingProvisioned(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingPayPerRequest(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingMode_provisionedToPayPerRequestIgnoreChanges(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingProvisioned(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingPayPerRequestIgnoreChanges(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingModeGSI_payPerRequestToProvisioned(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingProvisionedGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_BillingModeGSI_provisionedToPayPerRequest(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_billingProvisionedGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_streamSpecification(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_streamSpecification(rName, true, "KEYS_ONLY"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"), + acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))), + resource.TestCheckResourceAttrSet(resourceName, "stream_label"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_streamSpecification(rName, false, ""), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "stream_view_type", ""), + acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))), + resource.TestCheckResourceAttrSet(resourceName, "stream_label"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_streamSpecificationValidation(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_streamSpecification("anything", true, ""), + ExpectError: regexp.MustCompile(`stream_view_type is required when stream_enabled = true`), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_tags(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_tags(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + testAccCheckInitialTableReplicaConf(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// https://github.com/hashicorp/terraform/issues/13243 +func TestAccDynamoDBTableReplica_gsiUpdateCapacity(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_gsiUpdate(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "1", + "write_capacity": "1", + "name": "att1-index", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "1", + "write_capacity": "1", + "name": "att2-index", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "1", + "write_capacity": "1", + "name": "att3-index", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_gsiUpdatedCapacity(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "2", + "write_capacity": "2", + "name": "att1-index", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "2", + "write_capacity": "2", + "name": "att2-index", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "read_capacity": "2", + "write_capacity": "2", + "name": "att3-index", + }), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_gsiUpdateOtherAttributes(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_gsiUpdate(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att3", + "name": "att3-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att1", + "name": "att1-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att2", + "name": "att2-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att4", + "name": "att2-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "att2", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att3", + "name": "att3-index", + "non_key_attributes.#": "1", + "projection_type": "INCLUDE", + "range_key": "att4", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att1", + "name": "att1-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + ), + }, + }, + }) +} + +// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/15115 +func TestAccDynamoDBTableReplica_lsiNonKeyAttributes(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_lsiNonKeyAttributes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ + "name": "TestTableLSI", + "non_key_attributes.#": "1", + "non_key_attributes.0": "TestNonKeyAttribute", + "projection_type": "INCLUDE", + "range_key": "TestLSIRangeKey", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// https://github.com/hashicorp/terraform-provider-aws/issues/566 +func TestAccDynamoDBTableReplica_gsiUpdateNonKeyAttributes(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att4", + "name": "att2-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "att2", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att3", + "name": "att3-index", + "non_key_attributes.#": "1", + "projection_type": "INCLUDE", + "range_key": "att4", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att1", + "name": "att1-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_gsiUpdatedNonKeyAttributes(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att4", + "name": "att2-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "att2", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att3", + "name": "att3-index", + "non_key_attributes.#": "2", + "projection_type": "INCLUDE", + "range_key": "att4", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "AnotherAttribute"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att1", + "name": "att1-index", + "non_key_attributes.#": "0", + "projection_type": "ALL", + "range_key": "", + "read_capacity": "1", + "write_capacity": "1", + }), + ), + }, + }, + }) +} + +// https://github.com/hashicorp/terraform-provider-aws/issues/671 +func TestAccDynamoDBTableReplica_GsiUpdateNonKeyAttributes_emptyPlan(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + attributes := fmt.Sprintf("%q, %q", "AnotherAttribute", "RandomAttribute") + reorderedAttributes := fmt.Sprintf("%q, %q", "RandomAttribute", "AnotherAttribute") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, attributes), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "hash_key": "att1", + "name": "att1-index", + "non_key_attributes.#": "2", + "projection_type": "INCLUDE", + "range_key": "att2", + "read_capacity": "1", + "write_capacity": "1", + }), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "AnotherAttribute"), + resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, reorderedAttributes), + 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 TestAccDynamoDBTableReplica_TTL_enabled(t *testing.T) { + var table dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_timeToLive(rName, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// 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 TestAccDynamoDBTableReplica_TTL_disabled(t *testing.T) { + var table dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_timeToLive(rName, false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_timeToLive(rName, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), + resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "true"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_attributeUpdate(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "S"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { // Attribute type change + Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "N"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + { // New attribute addition (index update) + Config: testAccTableReplicaConfig_twoAttributes(rName, "firstKey", "secondKey", "firstKey", "N", "secondKey", "S"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + { // Attribute removal (index update) + Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "S"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_lsiUpdate(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_lsi(rName, "lsi-original"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { // Change name of local secondary index + Config: testAccTableReplicaConfig_lsi(rName, "lsi-changed"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_attributeUpdateValidation(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "unusedKey", "S"), + ExpectError: regexp.MustCompile(`attributes must be indexed. Unused attributes: \["unusedKey"\]`), + }, + { + Config: testAccTableReplicaConfig_twoAttributes(rName, "firstKey", "secondKey", "firstUnused", "N", "secondUnused", "S"), + ExpectError: regexp.MustCompile(`attributes must be indexed. Unused attributes: \["firstUnused"\ \"secondUnused\"]`), + }, + { + Config: testAccTableReplicaConfig_unmatchedIndexes(rName, "firstUnused", "secondUnused"), + ExpectError: regexp.MustCompile(`indexes must match a defined attribute. Unmatched indexes:`), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_encryption(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var confBYOK, confEncEnabled, confEncDisabled dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + kmsKeyResourceName := "aws_kms_key.test" + kmsAliasDatasourceName := "data.aws_kms_alias.dynamodb" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_initialStateEncryptionBYOK(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName, false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &confEncDisabled), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), + func(s *terraform.State) error { + if !confEncDisabled.Table.CreationDateTime.Equal(*confBYOK.Table.CreationDateTime) { + return fmt.Errorf("DynamoDB table recreated when changing SSE") + } + return nil + }, + ), + }, + { + Config: testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &confEncEnabled), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsAliasDatasourceName, "target_key_arn"), + func(s *terraform.State) error { + if !confEncEnabled.Table.CreationDateTime.Equal(*confEncDisabled.Table.CreationDateTime) { + return fmt.Errorf("DynamoDB table recreated when changing SSE") + } + return nil + }, + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_multiple(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var table dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 3) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replica2(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + ), + }, + { + Config: testAccTableReplicaConfig_replica2(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_replica0(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "replica.#", "0"), + ), + }, + { + Config: testAccTableReplicaConfig_replica2(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_single(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replica1(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + ), + }, + { + Config: testAccTableReplicaConfig_replica1(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_replica0(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "0"), + ), + }, + { + Config: testAccTableReplicaConfig_replica1(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_tagMagic(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replicaMoreTags(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + ), + }, + { + Config: testAccTableReplicaConfig_replicaMoreTags(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_singleWithCMK(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + kmsKeyResourceName := "aws_kms_key.test" + // kmsAliasDatasourceName := "data.aws_kms_alias.master" + kmsKeyReplicaResourceName := "aws_kms_key.alt_test" + // kmsAliasReplicaDatasourceName := "data.aws_kms_alias.replica" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replicaCMK(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "replica.0.kms_key_arn", kmsKeyReplicaResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_pitr(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replicaPITR(rName, false, true, false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "point_in_time_recovery": "true", + "region_name": acctest.AlternateRegion(), + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "point_in_time_recovery": "false", + "region_name": acctest.ThirdRegion(), + }), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "false"), + ), + }, + { + Config: testAccTableReplicaConfig_replicaPITR(rName, true, false, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "point_in_time_recovery": "false", + "region_name": acctest.AlternateRegion(), + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "point_in_time_recovery": "true", + "region_name": acctest.ThirdRegion(), + }), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "true"), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_tagsOneOfTwo(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replicaTags(rName, "benny", "smiles", true, false), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaHasTags(resourceName, acctest.AlternateRegion(), true), + testAccCheckTableReplicaHasTags(resourceName, acctest.ThirdRegion(), false), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "region_name": acctest.AlternateRegion(), + "propagate_tags": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "region_name": acctest.ThirdRegion(), + "propagate_tags": "false", + }), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_Replica_tagsTwoOfTwo(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_replicaTags(rName, "Structure", "Adobe", true, true), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaHasTags(resourceName, acctest.AlternateRegion(), true), + testAccCheckTableReplicaHasTags(resourceName, acctest.ThirdRegion(), true), + resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "region_name": acctest.AlternateRegion(), + "propagate_tags": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ + "region_name": acctest.ThirdRegion(), + "propagate_tags": "true", + }), + ), + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_tableClassInfrequentAccess(t *testing.T) { + var table dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_class(rName, "STANDARD_INFREQUENT_ACCESS"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "table_class", "STANDARD_INFREQUENT_ACCESS"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_class(rName, "STANDARD"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &table), + resource.TestCheckResourceAttr(resourceName, "table_class", "STANDARD"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_backupEncryption(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var confBYOK dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + kmsKeyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_backupInitialStateEncryption(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "restore_to_latest_time", + "restore_date_time", + "restore_source_name", + }, + }, + }, + }) +} + +func TestAccDynamoDBTableReplica_backup_overrideEncryption(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var confBYOK dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + kmsKeyResourceName := "aws_kms_key.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTableReplicaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableReplicaConfig_backupInitialStateOverrideEncryption(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), + resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "restore_to_latest_time", + "restore_date_time", + "restore_source_name", + }, + }, + }, + }) +} + +func testAccCheckTableReplicaDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_dynamodb_table" { + continue + } + + log.Printf("[DEBUG] Checking if DynamoDB table %s exists", rs.Primary.ID) + // Check if queue exists by checking for its attributes + params := &dynamodb.DescribeTableInput{ + TableName: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeTable(params) + if err == nil { + return fmt.Errorf("DynamoDB table %s still exists. Failing!", rs.Primary.ID) + } + + if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { + continue + } + + return err + } + + return nil +} + +func testAccCheckInitialTableReplicaExists(n string, table *dynamodb.DescribeTableOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + log.Printf("[DEBUG] Trying to create initial table state!") + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No DynamoDB table name specified!") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn + + params := &dynamodb.DescribeTableInput{ + TableName: aws.String(rs.Primary.ID), + } + + resp, err := conn.DescribeTable(params) + + if err != nil { + return fmt.Errorf("Problem describing table '%s': %s", rs.Primary.ID, err) + } + + *table = *resp + + return nil + } +} + +func testAccCheckTableReplicaHasTags(n string, region string, should bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No DynamoDB table name specified!") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn + terraformVersion := acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion + + if aws.StringValue(conn.Config.Region) != region { + session, err := conns.NewSessionForRegion(&conn.Config, region, terraformVersion) + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.ID, err) + } + + conn = dynamodb.New(session) + } + + newARN, err := tfdynamodb.ARNForNewRegion(rs.Primary.Attributes["arn"], region) + + if err != nil { + return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.ID, err) + } + + tags, err := tfdynamodb.ListTags(conn, newARN) + + if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { + return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], err) + } + + if len(tags.Keys()) > 0 && !should { + return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should not have tags but does")) + } + + if len(tags.Keys()) == 0 && should { + return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should have tags but does not")) + } + + return nil + } +} + +func testAccCheckInitialTableReplicaConf(resourceName string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "hash_key", "TestTableHashKey"), + resource.TestCheckResourceAttr(resourceName, "range_key", "TestTableRangeKey"), + resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + resource.TestCheckResourceAttr(resourceName, "write_capacity", "2"), + resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), + resource.TestCheckResourceAttr(resourceName, "attribute.#", "4"), + resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "1"), + resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestTableHashKey", + "type": "S", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestTableRangeKey", + "type": "S", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestLSIRangeKey", + "type": "N", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ + "name": "TestGSIRangeKey", + "type": "S", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ + "name": "InitialTestTableGSI", + "hash_key": "TestTableHashKey", + "range_key": "TestGSIRangeKey", + "write_capacity": "1", + "read_capacity": "1", + "projection_type": "KEYS_ONLY", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ + "name": "TestTableLSI", + "range_key": "TestLSIRangeKey", + "projection_type": "ALL", + }), + ) +} + +func testAccTableReplicaConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 1 + hash_key = %[1]q + + attribute { + name = %[1]q + type = "S" + } +} +`, rName) +} + +func testAccTableReplicaConfig_backup(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 1 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + point_in_time_recovery { + enabled = true + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingPayPerRequest(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + billing_mode = "PAY_PER_REQUEST" + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingPayPerRequestIgnoreChanges(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + billing_mode = "PAY_PER_REQUEST" + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + lifecycle { + ignore_changes = [read_capacity, write_capacity] + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingProvisioned(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + billing_mode = "PROVISIONED" + hash_key = "TestTableHashKey" + + read_capacity = 5 + write_capacity = 5 + + attribute { + name = "TestTableHashKey" + type = "S" + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingProvisionedIgnoreChanges(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + billing_mode = "PROVISIONED" + hash_key = "TestTableHashKey" + + read_capacity = 5 + write_capacity = 5 + + attribute { + name = "TestTableHashKey" + type = "S" + } + + lifecycle { + ignore_changes = [read_capacity, write_capacity] + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingPayPerRequestGSI(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + billing_mode = "PAY_PER_REQUEST" + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableGSIKey" + type = "S" + } + + global_secondary_index { + name = "TestTableGSI" + hash_key = "TestTableGSIKey" + projection_type = "KEYS_ONLY" + } +} +`, rName) +} + +func testAccTableReplicaConfig_billingProvisionedGSI(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + billing_mode = "PROVISIONED" + hash_key = "TestTableHashKey" + name = %[1]q + read_capacity = 1 + write_capacity = 1 + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableGSIKey" + type = "S" + } + + global_secondary_index { + hash_key = "TestTableGSIKey" + name = "TestTableGSI" + projection_type = "KEYS_ONLY" + read_capacity = 1 + write_capacity = 1 + } +} +`, rName) +} + +func testAccTableReplicaConfig_initialState(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 2 + hash_key = "TestTableHashKey" + range_key = "TestTableRangeKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableRangeKey" + type = "S" + } + + attribute { + name = "TestLSIRangeKey" + type = "N" + } + + attribute { + name = "TestGSIRangeKey" + type = "S" + } + + local_secondary_index { + name = "TestTableLSI" + range_key = "TestLSIRangeKey" + projection_type = "ALL" + } + + global_secondary_index { + name = "InitialTestTableGSI" + hash_key = "TestTableHashKey" + range_key = "TestGSIRangeKey" + write_capacity = 1 + read_capacity = 1 + projection_type = "KEYS_ONLY" + } +} +`, rName) +} + +func testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName string, enabled bool) string { + return fmt.Sprintf(` +data "aws_kms_alias" "dynamodb" { + name = "alias/aws/dynamodb" +} + +resource "aws_kms_key" "test" { + description = %[1]q +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 1 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + server_side_encryption { + enabled = %[2]t + } +} +`, rName, enabled) +} + +func testAccTableReplicaConfig_initialStateEncryptionBYOK(rName string) string { + return fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 2 + write_capacity = 2 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.test.arn + } +} +`, rName) +} + +func testAccTableReplicaConfig_addSecondaryGSI(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 2 + write_capacity = 2 + hash_key = "TestTableHashKey" + range_key = "TestTableRangeKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableRangeKey" + type = "S" + } + + attribute { + name = "TestLSIRangeKey" + type = "N" + } + + attribute { + name = "ReplacementGSIRangeKey" + type = "N" + } + + local_secondary_index { + name = "TestTableLSI" + range_key = "TestLSIRangeKey" + projection_type = "ALL" + } + + global_secondary_index { + name = "ReplacementTestTableGSI" + hash_key = "TestTableHashKey" + range_key = "ReplacementGSIRangeKey" + write_capacity = 5 + read_capacity = 5 + projection_type = "INCLUDE" + non_key_attributes = ["TestNonKeyAttribute"] + } +} +`, rName) +} + +func testAccTableReplicaConfig_streamSpecification(rName string, enabled bool, viewType string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 2 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + stream_enabled = %[2]t + stream_view_type = %[3]q +} +`, rName, enabled, viewType) +} + +func testAccTableReplicaConfig_tags(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 2 + hash_key = "TestTableHashKey" + range_key = "TestTableRangeKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableRangeKey" + type = "S" + } + + attribute { + name = "TestLSIRangeKey" + type = "N" + } + + attribute { + name = "TestGSIRangeKey" + type = "S" + } + + local_secondary_index { + name = "TestTableLSI" + range_key = "TestLSIRangeKey" + projection_type = "ALL" + } + + global_secondary_index { + name = "InitialTestTableGSI" + hash_key = "TestTableHashKey" + range_key = "TestGSIRangeKey" + write_capacity = 1 + read_capacity = 1 + projection_type = "KEYS_ONLY" + } + + tags = { + Name = %[1]q + AccTest = "yes" + Testing = "absolutely" + } +} +`, rName) +} + +func testAccTableReplicaConfig_gsiUpdate(rName string) string { + return fmt.Sprintf(` +variable "capacity" { + default = 1 +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = var.capacity + write_capacity = var.capacity + hash_key = "id" + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "att1" + type = "S" + } + + attribute { + name = "att2" + type = "S" + } + + attribute { + name = "att3" + type = "S" + } + + global_secondary_index { + name = "att1-index" + hash_key = "att1" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att2-index" + hash_key = "att2" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att3-index" + hash_key = "att3" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } +} +`, rName) +} + +func testAccTableReplicaConfig_gsiUpdatedCapacity(rName string) string { + return fmt.Sprintf(` +variable "capacity" { + default = 2 +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = var.capacity + write_capacity = var.capacity + hash_key = "id" + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "att1" + type = "S" + } + + attribute { + name = "att2" + type = "S" + } + + attribute { + name = "att3" + type = "S" + } + + global_secondary_index { + name = "att1-index" + hash_key = "att1" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att2-index" + hash_key = "att2" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att3-index" + hash_key = "att3" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } +} +`, rName) +} + +func testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName string) string { + return fmt.Sprintf(` +variable "capacity" { + default = 1 +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = var.capacity + write_capacity = var.capacity + hash_key = "id" + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "att1" + type = "S" + } + + attribute { + name = "att2" + type = "S" + } + + attribute { + name = "att3" + type = "S" + } + + attribute { + name = "att4" + type = "S" + } + + global_secondary_index { + name = "att1-index" + hash_key = "att1" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att2-index" + hash_key = "att4" + range_key = "att2" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att3-index" + hash_key = "att3" + range_key = "att4" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "INCLUDE" + non_key_attributes = ["RandomAttribute"] + } +} +`, rName) +} + +func testAccTableReplicaConfig_gsiUpdatedNonKeyAttributes(rName string) string { + return fmt.Sprintf(` +variable "capacity" { + default = 1 +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = var.capacity + write_capacity = var.capacity + hash_key = "id" + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "att1" + type = "S" + } + + attribute { + name = "att2" + type = "S" + } + + attribute { + name = "att3" + type = "S" + } + + attribute { + name = "att4" + type = "S" + } + + global_secondary_index { + name = "att1-index" + hash_key = "att1" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att2-index" + hash_key = "att4" + range_key = "att2" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "ALL" + } + + global_secondary_index { + name = "att3-index" + hash_key = "att3" + range_key = "att4" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "INCLUDE" + non_key_attributes = ["RandomAttribute", "AnotherAttribute"] + } +} +`, rName) +} + +func testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, attributes string) string { + return fmt.Sprintf(` +variable "capacity" { + default = 1 +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = var.capacity + write_capacity = var.capacity + hash_key = "id" + + attribute { + name = "id" + type = "S" + } + + attribute { + name = "att1" + type = "S" + } + + attribute { + name = "att2" + type = "S" + } + + global_secondary_index { + name = "att1-index" + hash_key = "att1" + range_key = "att2" + write_capacity = var.capacity + read_capacity = var.capacity + projection_type = "INCLUDE" + non_key_attributes = [%s] + } +} +`, rName, attributes) +} + +func testAccTableReplicaConfig_lsiNonKeyAttributes(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + range_key = "TestTableRangeKey" + write_capacity = 1 + read_capacity = 1 + + attribute { + name = "TestTableHashKey" + type = "S" + } + + attribute { + name = "TestTableRangeKey" + type = "S" + } + + attribute { + name = "TestLSIRangeKey" + type = "N" + } + + local_secondary_index { + name = "TestTableLSI" + range_key = "TestLSIRangeKey" + projection_type = "INCLUDE" + non_key_attributes = ["TestNonKeyAttribute"] + } +} +`, rName) +} + +func testAccTableReplicaConfig_timeToLive(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 = %[2]t ? "TestTTL" : "" + enabled = %[2]t + } +} +`, rName, ttlEnabled) +} + +func testAccTableReplicaConfig_oneAttribute(rName, hashKey, attrName, attrType string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 10 + write_capacity = 10 + hash_key = "staticHashKey" + + attribute { + name = "staticHashKey" + type = "S" + } + + attribute { + name = %[3]q + type = %[4]q + } + + global_secondary_index { + name = "gsiName" + hash_key = %[2]q + write_capacity = 10 + read_capacity = 10 + projection_type = "KEYS_ONLY" + } +} +`, rName, hashKey, attrName, attrType) +} + +func testAccTableReplicaConfig_twoAttributes(rName, hashKey, rangeKey, attrName1, attrType1, attrName2, attrType2 string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 10 + write_capacity = 10 + hash_key = "staticHashKey" + + attribute { + name = "staticHashKey" + type = "S" + } + + attribute { + name = %[4]q + type = %[5]q + } + + attribute { + name = %[6]q + type = %[7]q + } + + global_secondary_index { + name = "gsiName" + hash_key = %[2]q + range_key = %[3]q + write_capacity = 10 + read_capacity = 10 + projection_type = "KEYS_ONLY" + } +} +`, rName, hashKey, rangeKey, attrName1, attrType1, attrName2, attrType2) +} + +func testAccTableReplicaConfig_unmatchedIndexes(rName, attr1, attr2 string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 10 + write_capacity = 10 + hash_key = "staticHashKey" + range_key = %[2]q + + attribute { + name = "staticHashKey" + type = "S" + } + + local_secondary_index { + name = "lsiName" + range_key = %[3]q + projection_type = "KEYS_ONLY" + } +} +`, rName, attr1, attr2) +} + +func testAccTableReplicaConfig_replica0(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } +} +`, rName)) +} + +func testAccTableReplicaConfig_replica1(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + } +} +`, rName)) +} + +func testAccTableReplicaConfig_replicaMoreTags(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + propagate_tags = true + + tags = { + ReplicaTag = "Bob" + } + } + + tags = { + Name = %[1]q + Pozo = "Amargo" + } +} +`, rName)) +} + +func testAccTableReplicaConfig_replicaCMK(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +resource "aws_kms_key" "test" { + description = %[1]q +} + +resource "aws_kms_key" "alt_test" { + provider = "awsalternate" + description = "%[1]s-2" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + kms_key_arn = aws_kms_key.alt_test.arn + } + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.test.arn + } + + timeouts { + create = "20m" + update = "20m" + delete = "20m" + } +} +`, rName)) +} + +func testAccTableReplicaConfig_replicaPITR(rName string, mainPITR, replica1, replica2 bool) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +data "aws_region" "third" { + provider = "awsthird" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + point_in_time_recovery { + enabled = %[2]t + } + + replica { + region_name = data.aws_region.alternate.name + point_in_time_recovery = %[3]t + } + + replica { + region_name = data.aws_region.third.name + point_in_time_recovery = %[4]t + } +} +`, rName, mainPITR, replica1, replica2)) +} + +func testAccTableReplicaConfig_replica2(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +data "aws_region" "third" { + provider = "awsthird" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + } + + replica { + region_name = data.aws_region.third.name + } +} +`, rName)) +} + +func testAccTableReplicaConfig_replicaTags(rName, key, value string, propagate1, propagate2 bool) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +data "aws_region" "third" { + provider = "awsthird" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + propagate_tags = %[4]t + } + + replica { + region_name = data.aws_region.third.name + propagate_tags = %[5]t + } + + tags = { + Name = %[1]q + Pozo = "Amargo" + %[2]s = %[3]q + } +} +`, rName, key, value, propagate1, propagate2)) +} + +func testAccTableReplicaConfig_lsi(rName, lsiName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 10 + write_capacity = 10 + hash_key = "staticHashKey" + range_key = "staticRangeKey" + + attribute { + name = "staticHashKey" + type = "S" + } + + attribute { + name = "staticRangeKey" + type = "S" + } + + attribute { + name = "staticLSIRangeKey" + type = "S" + } + + local_secondary_index { + name = %[2]q + range_key = "staticLSIRangeKey" + projection_type = "KEYS_ONLY" + } +} +`, rName, lsiName) +} + +func testAccTableReplicaConfig_class(rName, tableClass string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "test" { + hash_key = "TestTableHashKey" + name = %[1]q + read_capacity = 1 + write_capacity = 1 + table_class = %[2]q + + attribute { + name = "TestTableHashKey" + type = "S" + } +} +`, rName, tableClass) +} + +func testAccTableReplicaConfig_backupInitialStateOverrideEncryption(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "source" { + name = "%[1]s-source" + read_capacity = 2 + write_capacity = 2 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = false + } +} + +resource "aws_kms_key" "test" { + description = %[1]q +} + +resource "aws_dynamodb_table" "test" { + name = "%[1]s-target" + restore_source_name = aws_dynamodb_table.source.name + restore_to_latest_time = true + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.test.arn + } +} +`, rName) +} + +func testAccTableReplicaConfig_backupInitialStateEncryption(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "source" { + name = "%[1]s-source" + read_capacity = 2 + write_capacity = 2 + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + point_in_time_recovery { + enabled = true + } + + server_side_encryption { + enabled = true + kms_key_arn = aws_kms_key.test.arn + } +} + +resource "aws_kms_key" "test" { + description = %[1]q +} + +resource "aws_dynamodb_table" "test" { + name = "%[1]s-target" + restore_source_name = aws_dynamodb_table.source.name + restore_to_latest_time = true +} +`, rName) +} diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index 5c807385987..e69ebf7abe0 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -1507,6 +1507,37 @@ func TestAccDynamoDBTable_Replica_single(t *testing.T) { }) } +func TestAccDynamoDBTable_Replica_tagMagic(t *testing.T) { + var conf dynamodb.DescribeTableOutput + resourceName := "aws_dynamodb_table.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration + CheckDestroy: testAccCheckTableDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTableConfig_replicaMoreTags(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckInitialTableExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), + ), + }, + { + Config: testAccTableConfig_replicaMoreTags(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccDynamoDBTable_Replica_singleWithCMK(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -2809,6 +2840,43 @@ resource "aws_dynamodb_table" "test" { `, rName)) } +func testAccTableConfig_replicaMoreTags(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + fmt.Sprintf(` +data "aws_region" "alternate" { + provider = "awsalternate" +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + hash_key = "TestTableHashKey" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "TestTableHashKey" + type = "S" + } + + replica { + region_name = data.aws_region.alternate.name + propagate_tags = true + + tags = { + ReplicaTag = "Bob" + } + } + + tags = { + Name = %[1]q + Pozo = "Amargo" + } +} +`, rName)) +} + func testAccTableConfig_replicaCMK(rName string) string { return acctest.ConfigCompose( acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors From d15e4c7b18c16f0da2d2611ed9168f88a93b1efd Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 9 Aug 2022 17:22:01 -0400 Subject: [PATCH 02/15] gofmt --- internal/acctest/acctest.go | 7 +-- internal/create/naming.go | 3 +- internal/service/acm/certificate_test.go | 4 +- .../certificate_authority_data_source_test.go | 2 +- internal/service/appstream/status.go | 8 ++-- .../cloudformation/stack_set_instance_test.go | 6 +-- .../service/cloudfront/distribution_test.go | 6 +-- .../service/connect/bot_association_test.go | 2 +- internal/service/connect/contact_flow_test.go | 2 +- .../connect/hours_of_operation_test.go | 2 +- internal/service/connect/instance_test.go | 2 +- .../lambda_function_association_test.go | 2 +- .../service/connect/quick_connect_test.go | 2 +- .../service/connect/security_profile_test.go | 2 +- .../connect/user_hierarchy_group_test.go | 2 +- .../connect/user_hierarchy_structure_test.go | 2 +- internal/service/connect/vocabulary_test.go | 2 +- internal/service/docdb/cluster_test.go | 4 +- internal/service/ec2/ec2_instance.go | 2 +- internal/service/ec2/ec2_instance_test.go | 18 +++++--- internal/service/ec2/filters.go | 14 +++--- internal/service/ec2/flex.go | 2 +- internal/service/ec2/vpc_security_group.go | 44 +++++++++---------- .../service/ec2/vpnclient_endpoint_test.go | 7 +-- internal/service/eks/arn.go | 10 ++--- .../elasticache/replication_group_test.go | 2 +- .../service/elastictranscoder/preset_test.go | 2 +- .../elb/hosted_zone_id_data_source_test.go | 2 +- .../elbv2/hosted_zone_id_data_source_test.go | 4 +- internal/service/events/target_test.go | 2 +- .../fsx/data_repository_association_test.go | 4 +- internal/service/iam/acc_test.go | 3 +- ...etwork_policy_document_data_source_test.go | 4 +- internal/service/networkmanager/errors.go | 10 ++--- internal/service/opsworks/stack.go | 4 +- internal/service/rds/instance_test.go | 4 +- internal/service/redshift/consts.go | 1 + internal/service/route53/flex.go | 30 ++++++++----- internal/service/sagemaker/model_test.go | 2 +- .../secretsmanager/secret_data_source_test.go | 2 +- .../private_dns_namespace_test.go | 4 +- internal/service/ssm/document.go | 2 +- internal/service/storagegateway/errors.go | 5 ++- internal/tags/tags.go | 1 - internal/tfresource/errors.go | 4 +- internal/verify/diff.go | 6 +-- 46 files changed, 136 insertions(+), 119 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index f00ac09f0a5..53fbef54c8e 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1649,9 +1649,10 @@ data "aws_availability_zones" "available" { // AvailableEC2InstanceTypeForAvailabilityZone returns the configuration for a data source that describes // the first available EC2 instance type offering in the specified availability zone from a list of preferred instance types. // The first argument is either an Availability Zone name or Terraform configuration reference to one, e.g. -// * data.aws_availability_zones.available.names[0] -// * aws_subnet.test.availability_zone -// * us-west-2a +// - data.aws_availability_zones.available.names[0] +// - aws_subnet.test.availability_zone +// - us-west-2a +// // The data source is named 'available'. func AvailableEC2InstanceTypeForAvailabilityZone(availabilityZoneName string, preferredInstanceTypes ...string) string { if !strings.Contains(availabilityZoneName, ".") { diff --git a/internal/create/naming.go b/internal/create/naming.go index 9d17cf58529..2bbe82862dc 100644 --- a/internal/create/naming.go +++ b/internal/create/naming.go @@ -39,8 +39,7 @@ func hasResourceUniqueIDPlusAdditionalSuffix(s string, additionalSuffix string) // // An expected usage might be: // -// d.Set("name_prefix", create.NamePrefixFromName(d.Id())) -// +// d.Set("name_prefix", create.NamePrefixFromName(d.Id())) func NamePrefixFromName(name string) *string { return NamePrefixFromNameWithSuffix(name, "") } diff --git a/internal/service/acm/certificate_test.go b/internal/service/acm/certificate_test.go index 53d7d5116e8..517a26df464 100644 --- a/internal/service/acm/certificate_test.go +++ b/internal/service/acm/certificate_test.go @@ -599,7 +599,7 @@ func TestAccACMCertificate_disableCTLogging(t *testing.T) { }) } -//lintignore:AT002 +// lintignore:AT002 func TestAccACMCertificate_Imported_domainName(t *testing.T) { resourceName := "aws_acm_certificate.test" commonName := "example.com" @@ -654,7 +654,7 @@ func TestAccACMCertificate_Imported_domainName(t *testing.T) { }) } -//lintignore:AT002 +// lintignore:AT002 func TestAccACMCertificate_Imported_ipAddress(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/7103 resourceName := "aws_acm_certificate.test" var v acm.CertificateDetail diff --git a/internal/service/acmpca/certificate_authority_data_source_test.go b/internal/service/acmpca/certificate_authority_data_source_test.go index 78f4ebbadc6..6190862c75c 100644 --- a/internal/service/acmpca/certificate_authority_data_source_test.go +++ b/internal/service/acmpca/certificate_authority_data_source_test.go @@ -156,7 +156,7 @@ data "aws_acmpca_certificate_authority" "test" { `, commonName) } -//lintignore:AWSAT003,AWSAT005 +// lintignore:AWSAT003,AWSAT005 const testAccCertificateAuthorityDataSourceConfig_nonExistent = ` data "aws_acmpca_certificate_authority" "test" { arn = "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/tf-acc-test-does-not-exist" diff --git a/internal/service/appstream/status.go b/internal/service/appstream/status.go index 8f81eddbcd6..4c7c22199c3 100644 --- a/internal/service/appstream/status.go +++ b/internal/service/appstream/status.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -//statusStackState fetches the fleet and its state +// statusStackState fetches the fleet and its state func statusStackState(ctx context.Context, conn *appstream.AppStream, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { stack, err := FindStackByName(ctx, conn, name) @@ -25,7 +25,7 @@ func statusStackState(ctx context.Context, conn *appstream.AppStream, name strin } } -//statusFleetState fetches the fleet and its state +// statusFleetState fetches the fleet and its state func statusFleetState(ctx context.Context, conn *appstream.AppStream, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { fleet, err := FindFleetByName(ctx, conn, name) @@ -42,7 +42,7 @@ func statusFleetState(ctx context.Context, conn *appstream.AppStream, name strin } } -//statusImageBuilderState fetches the ImageBuilder and its state +// statusImageBuilderState fetches the ImageBuilder and its state func statusImageBuilderState(ctx context.Context, conn *appstream.AppStream, name string) resource.StateRefreshFunc { return func() (interface{}, string, error) { imageBuilder, err := FindImageBuilderByName(ctx, conn, name) @@ -59,7 +59,7 @@ func statusImageBuilderState(ctx context.Context, conn *appstream.AppStream, nam } } -//statusUserAvailable fetches the user available +// statusUserAvailable fetches the user available func statusUserAvailable(ctx context.Context, conn *appstream.AppStream, username, authType string) resource.StateRefreshFunc { return func() (interface{}, string, error) { user, err := FindUserByUserNameAndAuthType(ctx, conn, username, authType) diff --git a/internal/service/cloudformation/stack_set_instance_test.go b/internal/service/cloudformation/stack_set_instance_test.go index b2f34a46414..1d3b944671a 100644 --- a/internal/service/cloudformation/stack_set_instance_test.go +++ b/internal/service/cloudformation/stack_set_instance_test.go @@ -165,9 +165,9 @@ func TestAccCloudFormationStackSetInstance_parameterOverrides(t *testing.T) { // TestAccCloudFormationStackSetInstance_retainStack verifies retain_stack = true // This acceptance test performs the following steps: -// * Trigger a Terraform destroy of the resource, which should only remove the instance from the StackSet -// * Check it still exists outside Terraform -// * Destroy for real outside Terraform +// - Trigger a Terraform destroy of the resource, which should only remove the instance from the StackSet +// - Check it still exists outside Terraform +// - Destroy for real outside Terraform func TestAccCloudFormationStackSetInstance_retainStack(t *testing.T) { var stack1 cloudformation.Stack var stackInstance1, stackInstance2, stackInstance3 cloudformation.StackInstance diff --git a/internal/service/cloudfront/distribution_test.go b/internal/service/cloudfront/distribution_test.go index 287d23b0ef8..0d81f5f7078 100644 --- a/internal/service/cloudfront/distribution_test.go +++ b/internal/service/cloudfront/distribution_test.go @@ -1025,9 +1025,9 @@ func TestAccCloudFrontDistribution_enabled(t *testing.T) { // TestAccCloudFrontDistribution_retainOnDelete verifies retain_on_delete = true // This acceptance test performs the following steps: -// * Trigger a Terraform destroy of the resource, which should only disable the distribution -// * Check it still exists and is disabled outside Terraform -// * Destroy for real outside Terraform +// - Trigger a Terraform destroy of the resource, which should only disable the distribution +// - Check it still exists and is disabled outside Terraform +// - Destroy for real outside Terraform func TestAccCloudFrontDistribution_retainOnDelete(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") diff --git a/internal/service/connect/bot_association_test.go b/internal/service/connect/bot_association_test.go index fd928ef5785..949dfa4068c 100644 --- a/internal/service/connect/bot_association_test.go +++ b/internal/service/connect/bot_association_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectBotAssociation_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccBotAssociation_basic, diff --git a/internal/service/connect/contact_flow_test.go b/internal/service/connect/contact_flow_test.go index ea6ef488b7d..7352af787e9 100644 --- a/internal/service/connect/contact_flow_test.go +++ b/internal/service/connect/contact_flow_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectContactFlow_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccContactFlow_basic, diff --git a/internal/service/connect/hours_of_operation_test.go b/internal/service/connect/hours_of_operation_test.go index 94be526d971..4c1e5dfcb8a 100644 --- a/internal/service/connect/hours_of_operation_test.go +++ b/internal/service/connect/hours_of_operation_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectHoursOfOperation_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccHoursOfOperation_basic, diff --git a/internal/service/connect/instance_test.go b/internal/service/connect/instance_test.go index b60f8d21578..3ce7f456c73 100644 --- a/internal/service/connect/instance_test.go +++ b/internal/service/connect/instance_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectInstance_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccInstance_basic, diff --git a/internal/service/connect/lambda_function_association_test.go b/internal/service/connect/lambda_function_association_test.go index c07d74460d4..d661a6e4dab 100644 --- a/internal/service/connect/lambda_function_association_test.go +++ b/internal/service/connect/lambda_function_association_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectLambdaFunctionAssociation_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccLambdaFunctionAssociation_basic, diff --git a/internal/service/connect/quick_connect_test.go b/internal/service/connect/quick_connect_test.go index 574a20c0242..198f4c76cf5 100644 --- a/internal/service/connect/quick_connect_test.go +++ b/internal/service/connect/quick_connect_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectQuickConnect_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccQuickConnect_phoneNumber, diff --git a/internal/service/connect/security_profile_test.go b/internal/service/connect/security_profile_test.go index 53bf24dbfda..02bc118e9c1 100644 --- a/internal/service/connect/security_profile_test.go +++ b/internal/service/connect/security_profile_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectSecurityProfile_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccSecurityProfile_basic, diff --git a/internal/service/connect/user_hierarchy_group_test.go b/internal/service/connect/user_hierarchy_group_test.go index 7e669b9fc79..d7f197ca18f 100644 --- a/internal/service/connect/user_hierarchy_group_test.go +++ b/internal/service/connect/user_hierarchy_group_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectUserHierarchyGroup_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccUserHierarchyGroup_basic, diff --git a/internal/service/connect/user_hierarchy_structure_test.go b/internal/service/connect/user_hierarchy_structure_test.go index 331a74dcba1..f7bc8409b0b 100644 --- a/internal/service/connect/user_hierarchy_structure_test.go +++ b/internal/service/connect/user_hierarchy_structure_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectUserHierarchyStructure_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccUserHierarchyStructure_basic, diff --git a/internal/service/connect/vocabulary_test.go b/internal/service/connect/vocabulary_test.go index 8c18505d249..63c1cced75b 100644 --- a/internal/service/connect/vocabulary_test.go +++ b/internal/service/connect/vocabulary_test.go @@ -15,7 +15,7 @@ import ( tfconnect "github.com/hashicorp/terraform-provider-aws/internal/service/connect" ) -//Serialized acceptance tests due to Connect account limits (max 2 parallel tests) +// Serialized acceptance tests due to Connect account limits (max 2 parallel tests) func TestAccConnectVocabulary_serial(t *testing.T) { testCases := map[string]func(t *testing.T){ "basic": testAccVocabulary_basic, diff --git a/internal/service/docdb/cluster_test.go b/internal/service/docdb/cluster_test.go index 434c240398d..04c3104cb25 100644 --- a/internal/service/docdb/cluster_test.go +++ b/internal/service/docdb/cluster_test.go @@ -368,8 +368,8 @@ func TestAccDocDBCluster_takeFinalSnapshot(t *testing.T) { }) } -/// This is a regression test to make sure that we always cover the scenario as hightlighted in -/// https://github.com/hashicorp/terraform/issues/11568 +// / This is a regression test to make sure that we always cover the scenario as hightlighted in +// / https://github.com/hashicorp/terraform/issues/11568 func TestAccDocDBCluster_missingUserNameCausesError(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, diff --git a/internal/service/ec2/ec2_instance.go b/internal/service/ec2/ec2_instance.go index 4b4a7878489..e6e3524aac4 100644 --- a/internal/service/ec2/ec2_instance.go +++ b/internal/service/ec2/ec2_instance.go @@ -2918,7 +2918,7 @@ func expandEnclaveOptions(l []interface{}) *ec2.EnclaveOptionsRequest { return opts } -//Expands an array of secondary Private IPs into a ec2 Private IP Address Spec +// Expands an array of secondary Private IPs into a ec2 Private IP Address Spec func expandSecondaryPrivateIPAddresses(ips []interface{}) []*ec2.PrivateIpAddressSpecification { specs := make([]*ec2.PrivateIpAddressSpecification, 0, len(ips)) for _, v := range ips { diff --git a/internal/service/ec2/ec2_instance_test.go b/internal/service/ec2/ec2_instance_test.go index 72269548b05..777a67af874 100644 --- a/internal/service/ec2/ec2_instance_test.go +++ b/internal/service/ec2/ec2_instance_test.go @@ -1937,7 +1937,8 @@ func TestAccEC2Instance_rootBlockDeviceMismatch(t *testing.T) { } // This test reproduces the bug here: -// https://github.com/hashicorp/terraform/issues/1752 +// +// https://github.com/hashicorp/terraform/issues/1752 // // I wish there were a way to exercise resources built with helper.Schema in a // unit context, in which case this test could be moved there, but for now this @@ -5020,8 +5021,9 @@ func testAccAvailableAZsWavelengthZonesDefaultExcludeConfig() string { } // testAccInstanceVPCConfig returns the configuration for tests that create -// 1) a VPC without IPv6 support -// 2) a subnet in the VPC that optionally assigns public IP addresses to ENIs +// 1. a VPC without IPv6 support +// 2. a subnet in the VPC that optionally assigns public IP addresses to ENIs +// // The resources are named 'test'. func testAccInstanceVPCConfig(rName string, mapPublicIpOnLaunch bool, azIndex int) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` @@ -5047,8 +5049,9 @@ resource "aws_subnet" "test" { } // testAccInstanceVPCSecurityGroupConfig returns the configuration for tests that create -// 1) a VPC security group -// 2) an internet gateway in the VPC +// 1. a VPC security group +// 2. an internet gateway in the VPC +// // The resources are named 'test'. func testAccInstanceVPCSecurityGroupConfig(rName string) string { return fmt.Sprintf(` @@ -5080,8 +5083,9 @@ resource "aws_security_group" "test" { } // testAccInstanceVPCIPv6Config returns the configuration for tests that create -// 1) a VPC with IPv6 support -// 2) a subnet in the VPC with an assigned IPv6 CIDR block +// 1. a VPC with IPv6 support +// 2. a subnet in the VPC with an assigned IPv6 CIDR block +// // The resources are named 'test'. func testAccInstanceVPCIPv6Config(rName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` diff --git a/internal/service/ec2/filters.go b/internal/service/ec2/filters.go index 2619ac41241..7fc26b6481c 100644 --- a/internal/service/ec2/filters.go +++ b/internal/service/ec2/filters.go @@ -25,9 +25,9 @@ import ( // In Terraform configuration this would then look like this, to constrain // results by name: // -// tags { -// Name = "my-awesome-subnet" -// } +// tags { +// Name = "my-awesome-subnet" +// } func BuildTagFilterList(tags []*ec2.Tag) []*ec2.Filter { filters := make([]*ec2.Filter, len(tags)) @@ -76,10 +76,10 @@ func attributeFiltersFromMultimap(m map[string][]string) []*ec2.Filter { // attributes or tags. In Terraform configuration, the custom filter blocks // then look like this: // -// filter { -// name = "availabilityZone" -// values = ["us-west-2a", "us-west-2b"] -// } +// filter { +// name = "availabilityZone" +// values = ["us-west-2a", "us-west-2b"] +// } func CustomFiltersSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeSet, diff --git a/internal/service/ec2/flex.go b/internal/service/ec2/flex.go index 297881cdcc0..b3ebe10e219 100644 --- a/internal/service/ec2/flex.go +++ b/internal/service/ec2/flex.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -//Flattens security group identifiers into a []string, where the elements returned are the GroupIDs +// Flattens security group identifiers into a []string, where the elements returned are the GroupIDs func FlattenGroupIdentifiers(dtos []*ec2.GroupIdentifier) []string { ids := make([]string, 0, len(dtos)) for _, v := range dtos { diff --git a/internal/service/ec2/vpc_security_group.go b/internal/service/ec2/vpc_security_group.go index 070b92412b0..649c57e053c 100644 --- a/internal/service/ec2/vpc_security_group.go +++ b/internal/service/ec2/vpc_security_group.go @@ -703,7 +703,6 @@ func updateSecurityGroupRules(conn *ec2.EC2, d *schema.ResourceData, ruleType st // remote rule, which may be structured differently because of how AWS // aggregates the rules under the to, from, and type. // -// // Matching rules are written to state, with their elements removed from the // remote set // @@ -1103,31 +1102,31 @@ func SecurityGroupCollapseRules(ruleset string, rules []interface{}) []interface // // For example, in terraform syntax, the following block: // -// ingress { -// from_port = 80 -// to_port = 80 -// protocol = "tcp" -// cidr_blocks = [ -// "192.168.0.1/32", -// "192.168.0.2/32", -// ] -// } +// ingress { +// from_port = 80 +// to_port = 80 +// protocol = "tcp" +// cidr_blocks = [ +// "192.168.0.1/32", +// "192.168.0.2/32", +// ] +// } // // will be converted to the two blocks below: // -// ingress { -// from_port = 80 -// to_port = 80 -// protocol = "tcp" -// cidr_blocks = [ "192.168.0.1/32" ] -// } +// ingress { +// from_port = 80 +// to_port = 80 +// protocol = "tcp" +// cidr_blocks = [ "192.168.0.1/32" ] +// } // -// ingress { -// from_port = 80 -// to_port = 80 -// protocol = "tcp" -// cidr_blocks = [ "192.168.0.2/32" ] -// } +// ingress { +// from_port = 80 +// to_port = 80 +// protocol = "tcp" +// cidr_blocks = [ "192.168.0.2/32" ] +// } // // Then the Difference operation is executed on the new set // to find which rules got modified, and the resulting set @@ -1135,7 +1134,6 @@ func SecurityGroupCollapseRules(ruleset string, rules []interface{}) []interface // to convert the "diff" back to a more compact form for // execution. Such compact form helps reduce the number of // API calls. -// func SecurityGroupExpandRules(rules *schema.Set) *schema.Set { var keys_to_expand = []string{"cidr_blocks", "ipv6_cidr_blocks", "prefix_list_ids", "security_groups"} diff --git a/internal/service/ec2/vpnclient_endpoint_test.go b/internal/service/ec2/vpnclient_endpoint_test.go index d51fa0159cf..c247b876727 100644 --- a/internal/service/ec2/vpnclient_endpoint_test.go +++ b/internal/service/ec2/vpnclient_endpoint_test.go @@ -26,9 +26,10 @@ func init() { } // This is part of an experimental feature, do not use this as a starting point for tests -// "This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. -// What is here was dangerous and repulsive to us. This message is a warning about danger." -// -- https://hyperallergic.com/312318/a-nuclear-warning-designed-to-last-10000-years/ +// +// "This place is not a place of honor... no highly esteemed deed is commemorated here... nothing valued is here. +// What is here was dangerous and repulsive to us. This message is a warning about danger." +// -- https://hyperallergic.com/312318/a-nuclear-warning-designed-to-last-10000-years/ func TestAccClientVPNEndpoint_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "Endpoint": { diff --git a/internal/service/eks/arn.go b/internal/service/eks/arn.go index c27de37707d..f1aacf98017 100644 --- a/internal/service/eks/arn.go +++ b/internal/service/eks/arn.go @@ -21,11 +21,11 @@ import ( // and converts STS assumed roles into the IAM role resource. // // Supported IAM resources are: -// * AWS account: arn:aws:iam::123456789012:root -// * IAM user: arn:aws:iam::123456789012:user/Bob -// * IAM role: arn:aws:iam::123456789012:role/S3Access -// * IAM Assumed role: arn:aws:sts::123456789012:assumed-role/Accounting-Role/Mary (converted to IAM role) -// * Federated user: arn:aws:sts::123456789012:federated-user/Bob +// - AWS account: arn:aws:iam::123456789012:root +// - IAM user: arn:aws:iam::123456789012:user/Bob +// - IAM role: arn:aws:iam::123456789012:role/S3Access +// - IAM Assumed role: arn:aws:sts::123456789012:assumed-role/Accounting-Role/Mary (converted to IAM role) +// - Federated user: arn:aws:sts::123456789012:federated-user/Bob func Canonicalize(arn string) (string, error) { parsed, err := awsarn.Parse(arn) if err != nil { diff --git a/internal/service/elasticache/replication_group_test.go b/internal/service/elasticache/replication_group_test.go index 2ad2eaaaeab..696460bc9ff 100644 --- a/internal/service/elasticache/replication_group_test.go +++ b/internal/service/elasticache/replication_group_test.go @@ -427,7 +427,7 @@ func TestAccElastiCacheReplicationGroup_updateNodeSize(t *testing.T) { }) } -//This is a test to prove that we panic we get in https://github.com/hashicorp/terraform/issues/9097 +// This is a test to prove that we panic we get in https://github.com/hashicorp/terraform/issues/9097 func TestAccElastiCacheReplicationGroup_updateParameterGroup(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") diff --git a/internal/service/elastictranscoder/preset_test.go b/internal/service/elastictranscoder/preset_test.go index 2df117b3503..24ff2aa024c 100644 --- a/internal/service/elastictranscoder/preset_test.go +++ b/internal/service/elastictranscoder/preset_test.go @@ -69,7 +69,7 @@ func TestAccElasticTranscoderPreset_video_noCodec(t *testing.T) { }) } -//https://github.com/terraform-providers/terraform-provider-aws/issues/14090 +// https://github.com/terraform-providers/terraform-provider-aws/issues/14090 func TestAccElasticTranscoderPreset_audio_noBitRate(t *testing.T) { var preset elastictranscoder.Preset resourceName := "aws_elastictranscoder_preset.test" diff --git a/internal/service/elb/hosted_zone_id_data_source_test.go b/internal/service/elb/hosted_zone_id_data_source_test.go index 5bd7ce07e93..23dc7d33e6d 100644 --- a/internal/service/elb/hosted_zone_id_data_source_test.go +++ b/internal/service/elb/hosted_zone_id_data_source_test.go @@ -35,7 +35,7 @@ const testAccHostedZoneIDDataSourceConfig_basic = ` data "aws_elb_hosted_zone_id" "main" {} ` -//lintignore:AWSAT003 +// lintignore:AWSAT003 const testAccHostedZoneIDDataSourceConfig_explicitRegion = ` data "aws_elb_hosted_zone_id" "regional" { region = "eu-west-1" diff --git a/internal/service/elbv2/hosted_zone_id_data_source_test.go b/internal/service/elbv2/hosted_zone_id_data_source_test.go index 7029f4e5339..d9386820e33 100644 --- a/internal/service/elbv2/hosted_zone_id_data_source_test.go +++ b/internal/service/elbv2/hosted_zone_id_data_source_test.go @@ -47,7 +47,7 @@ const testAccHostedZoneIDDataSourceConfig_basic = ` data "aws_lb_hosted_zone_id" "main" {} ` -//lintignore:AWSAT003 +// lintignore:AWSAT003 const testAccHostedZoneIDDataSourceConfig_explicitRegion = ` data "aws_lb_hosted_zone_id" "regional" { region = "eu-west-1" @@ -60,7 +60,7 @@ data "aws_lb_hosted_zone_id" "network" { } ` -//lintignore:AWSAT003 +// lintignore:AWSAT003 const testAccHostedZoneIDDataSourceConfig_explicitNetworkRegion = ` data "aws_lb_hosted_zone_id" "network-regional" { region = "eu-west-1" diff --git a/internal/service/events/target_test.go b/internal/service/events/target_test.go index 697d76028a2..d1cfc41ed80 100644 --- a/internal/service/events/target_test.go +++ b/internal/service/events/target_test.go @@ -367,7 +367,7 @@ func TestAccEventsTarget_http(t *testing.T) { }) } -//https://github.com/hashicorp/terraform-provider-aws/issues/23805 +// https://github.com/hashicorp/terraform-provider-aws/issues/23805 func TestAccEventsTarget_http_params(t *testing.T) { resourceName := "aws_cloudwatch_event_target.test" diff --git a/internal/service/fsx/data_repository_association_test.go b/internal/service/fsx/data_repository_association_test.go index f32af229909..ca6ed214f23 100644 --- a/internal/service/fsx/data_repository_association_test.go +++ b/internal/service/fsx/data_repository_association_test.go @@ -200,7 +200,7 @@ func TestAccFSxDataRepositoryAssociation_dataRepositoryPathUpdated(t *testing.T) }) } -//lintignore:AT002 +// lintignore:AT002 func TestAccFSxDataRepositoryAssociation_importedFileChunkSize(t *testing.T) { if acctest.Partition() == endpoints.AwsUsGovPartitionID { t.Skip("PERSISTENT_2 deployment_type is not supported in GovCloud partition") @@ -234,7 +234,7 @@ func TestAccFSxDataRepositoryAssociation_importedFileChunkSize(t *testing.T) { }) } -//lintignore:AT002 +// lintignore:AT002 func TestAccFSxDataRepositoryAssociation_importedFileChunkSizeUpdated(t *testing.T) { if acctest.Partition() == endpoints.AwsUsGovPartitionID { t.Skip("PERSISTENT_2 deployment_type is not supported in GovCloud partition") diff --git a/internal/service/iam/acc_test.go b/internal/service/iam/acc_test.go index 77c7e22ba71..68f309d9362 100644 --- a/internal/service/iam/acc_test.go +++ b/internal/service/iam/acc_test.go @@ -15,7 +15,8 @@ import ( // RandSSHKeyPair generates a public and private SSH key pair. The public key is // returned in OpenSSH format, and the private key is PEM encoded. // Copied from github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest, -// with the addition of the key size +// +// with the addition of the key size func RandSSHKeyPairSize(keySize int, comment string) (string, string, error) { privateKey, privateKeyPEM, err := genPrivateKey(keySize) if err != nil { diff --git a/internal/service/networkmanager/core_network_policy_document_data_source_test.go b/internal/service/networkmanager/core_network_policy_document_data_source_test.go index e3b2a70b755..25a001d4160 100644 --- a/internal/service/networkmanager/core_network_policy_document_data_source_test.go +++ b/internal/service/networkmanager/core_network_policy_document_data_source_test.go @@ -29,7 +29,7 @@ func TestAccNetworkManagerCoreNetworkPolicyDocumentDataSource_basic(t *testing.T }) } -//lintignore:AWSAT003 +// lintignore:AWSAT003 var testAccCoreNetworkPolicyDocumentDataSourceConfig_basic = ` data "aws_networkmanager_core_network_policy_document" "test" { core_network_configuration { @@ -271,7 +271,7 @@ data "aws_networkmanager_core_network_policy_document" "test" { } ` -//lintignore:AWSAT003 +// lintignore:AWSAT003 func testAccPolicyDocumentExpectedJSON() string { return `{ "version": "2021.12", diff --git a/internal/service/networkmanager/errors.go b/internal/service/networkmanager/errors.go index a8ee8e1a4eb..7e4943e270c 100644 --- a/internal/service/networkmanager/errors.go +++ b/internal/service/networkmanager/errors.go @@ -9,8 +9,8 @@ import ( ) // resourceNotFoundExceptionResourceIDEquals returns true if the error matches all these conditions: -// * err is of type networkmanager.ResourceNotFoundException -// * ResourceNotFoundException.ResourceId equals resourceID +// - err is of type networkmanager.ResourceNotFoundException +// - ResourceNotFoundException.ResourceId equals resourceID func resourceNotFoundExceptionResourceIDEquals(err error, resourceID string) bool { var resourceNotFoundException *networkmanager.ResourceNotFoundException @@ -22,9 +22,9 @@ func resourceNotFoundExceptionResourceIDEquals(err error, resourceID string) boo } // validationExceptionMessageContains returns true if the error matches all these conditions: -// * err is of type networkmanager.ValidationException -// * ValidationException.Reason equals reason -// * ValidationException.Fields.Message contains message +// - err is of type networkmanager.ValidationException +// - ValidationException.Reason equals reason +// - ValidationException.Fields.Message contains message func validationExceptionMessageContains(err error, reason string, message string) bool { var validationException *networkmanager.ValidationException diff --git a/internal/service/opsworks/stack.go b/internal/service/opsworks/stack.go index 577f52d3b84..b6781c32d54 100644 --- a/internal/service/opsworks/stack.go +++ b/internal/service/opsworks/stack.go @@ -411,8 +411,8 @@ func resourceStackRead(d *schema.ResourceData, meta interface{}) error { // in which they are created, so we allow users to specify an original endpoint // for Stacks created before multiple endpoints were offered (Terraform v0.9.0). // See: -// - https://github.com/hashicorp/terraform/pull/12688 -// - https://github.com/hashicorp/terraform/issues/12842 +// - https://github.com/hashicorp/terraform/pull/12688 +// - https://github.com/hashicorp/terraform/issues/12842 func connForRegion(region string, meta interface{}) (*opsworks.OpsWorks, error) { originalConn := meta.(*conns.AWSClient).OpsWorksConn diff --git a/internal/service/rds/instance_test.go b/internal/service/rds/instance_test.go index 39ebee990db..134b2fc5d1a 100644 --- a/internal/service/rds/instance_test.go +++ b/internal/service/rds/instance_test.go @@ -1709,7 +1709,9 @@ func TestAccRDSInstance_ReplicateSourceDB_replicaMode(t *testing.T) { // When an RDS Instance is added in a separate apply from the creation of the source instance, and the // parameter group is changed on the replica, it can sometimes lead to the API trying to reboot the instance -// whenanother "management operation" is in progress: +// +// whenanother "management operation" is in progress: +// // InvalidDBInstanceState: Instance cannot currently reboot due to an in-progress management operation // https://github.com/hashicorp/terraform-provider-aws/issues/11905 func TestAccRDSInstance_ReplicateSourceDB_parameterGroupTwoStep(t *testing.T) { diff --git a/internal/service/redshift/consts.go b/internal/service/redshift/consts.go index db83814d2d5..9c9af84578d 100644 --- a/internal/service/redshift/consts.go +++ b/internal/service/redshift/consts.go @@ -12,6 +12,7 @@ const ( ) // https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html#rs-mgmt-cluster-status. +// //nolint:deadcode,varcheck // These constants are missing from the AWS SDK const ( clusterStatusAvailable = "available" diff --git a/internal/service/route53/flex.go b/internal/service/route53/flex.go index d16e6b66077..09a4ab39b27 100644 --- a/internal/service/route53/flex.go +++ b/internal/service/route53/flex.go @@ -37,14 +37,16 @@ func FlattenResourceRecords(recs []*route53.ResourceRecord, typeStr string) []st // // In the Route 53, TXT entries are written using quoted strings, one per line. // Example: -// "x=foo" -// "bar=12" +// +// "x=foo" +// "bar=12" // // In Terraform, there are two differences: // - We use a list of strings instead of separating strings with newlines. // - Within each string, we dont' include the surrounding quotes. // Example: -// records = ["x=foo", "bar=12"] # Instead of ["\"x=foo\", \"bar=12\""] +// +// records = ["x=foo", "bar=12"] # Instead of ["\"x=foo\", \"bar=12\""] // // When we pull from Route 53, `expandTxtEntry` removes the surrounding quotes; // when we push to Route 53, `flattenTxtEntry` adds them back. @@ -52,13 +54,15 @@ func FlattenResourceRecords(recs []*route53.ResourceRecord, typeStr string) []st // One complication is that a single TXT entry can have multiple quoted strings. // For example, here are two TXT entries, one with two quoted strings and the // other with three. -// "x=" "foo" -// "ba" "r" "=12" +// +// "x=" "foo" +// "ba" "r" "=12" // // DNS clients are expected to merge the quoted strings before interpreting the // value. Since `expandTxtEntry` only removes the quotes at the end we can still // (hackily) represent the above configuration in Terraform: -// records = ["x=\" \"foo", "ba\" \"r\" \"=12"] +// +// records = ["x=\" \"foo", "ba\" \"r\" \"=12"] // // The primary reason to use multiple strings for an entry is that DNS (and Route // 53) doesn't allow a quoted string to be more than 255 characters long. If you @@ -66,14 +70,20 @@ func FlattenResourceRecords(recs []*route53.ResourceRecord, typeStr string) []st // // It would be nice if this Terraform automatically split strings longer than 255 // characters. For example, imagine "xxx..xxx" has 256 "x" characters. -// records = ["xxx..xxx"] +// +// records = ["xxx..xxx"] +// // When pushing to Route 53, this could be converted to: -// "xxx..xx" "x" +// +// "xxx..xx" "x" // // This could also work when the user is already using multiple quoted strings: -// records = ["xxx.xxx\" \"yyy..yyy"] +// +// records = ["xxx.xxx\" \"yyy..yyy"] +// // When pushing to Route 53, this could be converted to: -// "xxx..xx" "xyyy...y" "yy" +// +// "xxx..xx" "xyyy...y" "yy" // // If you want to add this feature, make sure to follow all the quoting rules in // . If you make a mistake, people diff --git a/internal/service/sagemaker/model_test.go b/internal/service/sagemaker/model_test.go index f04700abc9d..2b88dcb5c18 100644 --- a/internal/service/sagemaker/model_test.go +++ b/internal/service/sagemaker/model_test.go @@ -767,7 +767,7 @@ resource "aws_security_group" "bar" { `, rName)) } -//lintignore:AWSAT003,AWSAT005 +// lintignore:AWSAT003,AWSAT005 func testAccModelConfig_primaryContainerPrivateDockerRegistry(rName string) string { return acctest.ConfigCompose(testAccModelConfigBase(rName), acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_sagemaker_model" "test" { diff --git a/internal/service/secretsmanager/secret_data_source_test.go b/internal/service/secretsmanager/secret_data_source_test.go index e04e72f8ac0..5686665a260 100644 --- a/internal/service/secretsmanager/secret_data_source_test.go +++ b/internal/service/secretsmanager/secret_data_source_test.go @@ -153,7 +153,7 @@ const testAccSecretDataSourceConfig_missingRequired = ` data "aws_secretsmanager_secret" "test" {} ` -//lintignore:AWSAT003,AWSAT005 +// lintignore:AWSAT003,AWSAT005 const testAccSecretDataSourceConfig_multipleSpecified = ` data "aws_secretsmanager_secret" "test" { arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:tf-acc-test-does-not-exist" diff --git a/internal/service/servicediscovery/private_dns_namespace_test.go b/internal/service/servicediscovery/private_dns_namespace_test.go index 22c2e0b04b7..55f522cbf37 100644 --- a/internal/service/servicediscovery/private_dns_namespace_test.go +++ b/internal/service/servicediscovery/private_dns_namespace_test.go @@ -102,8 +102,8 @@ func TestAccServiceDiscoveryPrivateDNSNamespace_description(t *testing.T) { } // This acceptance test ensures we properly send back error messaging. References: -// * https://github.com/hashicorp/terraform-provider-aws/issues/2830 -// * https://github.com/hashicorp/terraform-provider-aws/issues/5532 +// - https://github.com/hashicorp/terraform-provider-aws/issues/2830 +// - https://github.com/hashicorp/terraform-provider-aws/issues/5532 func TestAccServiceDiscoveryPrivateDNSNamespace_Error_overlap(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) diff --git a/internal/service/ssm/document.go b/internal/service/ssm/document.go index b5c6f14cd98..d3403c966f9 100644 --- a/internal/service/ssm/document.go +++ b/internal/service/ssm/document.go @@ -713,7 +713,7 @@ func updateDocument(d *schema.ResourceData, meta interface{}) error { return nil } -//Validates that type and account_ids are defined +// Validates that type and account_ids are defined func ValidDocumentPermissions(v map[string]interface{}) (errors []error) { k := "permissions" t, hasType := v["type"].(string) diff --git a/internal/service/storagegateway/errors.go b/internal/service/storagegateway/errors.go index 24e2078db69..33bc7e35d50 100644 --- a/internal/service/storagegateway/errors.go +++ b/internal/service/storagegateway/errors.go @@ -16,8 +16,9 @@ const ( ) // operationErrorCode returns the operation error code from the specified error: -// * err is of type awserr.Error and represents a storagegateway.InternalServerError or storagegateway.InvalidGatewayRequestException -// * Error_ is not nil +// - err is of type awserr.Error and represents a storagegateway.InternalServerError or storagegateway.InvalidGatewayRequestException +// - Error_ is not nil +// // See https://docs.aws.amazon.com/storagegateway/latest/userguide/AWSStorageGatewayAPI.html#APIErrorResponses for details. func operationErrorCode(err error) string { if inner := (*storagegateway.InternalServerError)(nil); errors.As(err, &inner) && inner.Error_ != nil { diff --git a/internal/tags/tags.go b/internal/tags/tags.go index 252c4f5df57..30b491fc7a8 100644 --- a/internal/tags/tags.go +++ b/internal/tags/tags.go @@ -5,7 +5,6 @@ import ( ) // TagsSchema returns the schema to use for tags. -// func TagsSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeMap, diff --git a/internal/tfresource/errors.go b/internal/tfresource/errors.go index 5952e82692f..64a936c30be 100644 --- a/internal/tfresource/errors.go +++ b/internal/tfresource/errors.go @@ -16,8 +16,8 @@ func NotFound(err error) bool { // TimedOut returns true if the error represents a "wait timed out" condition. // Specifically, TimedOut returns true if the error matches all these conditions: -// * err is of type resource.TimeoutError -// * TimeoutError.LastError is nil +// - err is of type resource.TimeoutError +// - TimeoutError.LastError is nil func TimedOut(err error) bool { // This explicitly does *not* match wrapped TimeoutErrors timeoutErr, ok := err.(*resource.TimeoutError) //nolint:errorlint // Explicitly does *not* match wrapped TimeoutErrors diff --git a/internal/verify/diff.go b/internal/verify/diff.go index 2dcb5978887..263789403ff 100644 --- a/internal/verify/diff.go +++ b/internal/verify/diff.go @@ -89,9 +89,9 @@ func SuppressEquivalentTypeStringBoolean(k, old, new string, d *schema.ResourceD } // SuppressMissingOptionalConfigurationBlock handles configuration block attributes in the following scenario: -// * The resource schema includes an optional configuration block with defaults -// * The API response includes those defaults to refresh into the Terraform state -// * The operator's configuration omits the optional configuration block +// - The resource schema includes an optional configuration block with defaults +// - The API response includes those defaults to refresh into the Terraform state +// - The operator's configuration omits the optional configuration block func SuppressMissingOptionalConfigurationBlock(k, old, new string, d *schema.ResourceData) bool { return old == "1" && new == "0" } From c582be2e86f4f869baf133d03cfeb529e143185d Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Tue, 9 Aug 2022 17:29:13 -0400 Subject: [PATCH 03/15] Migrate to create.Error --- internal/service/dynamodb/table_replica.go | 51 ++++++++++--------- .../service/dynamodb/table_replica_test.go | 11 ++-- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go index a0ba586e54d..34608aa2b0f 100644 --- a/internal/service/dynamodb/table_replica.go +++ b/internal/service/dynamodb/table_replica.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -105,11 +106,11 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) } if globalRegion == aws.StringValue(conn.Config.Region) { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as global table")) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as global table")) } session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) @@ -187,7 +188,7 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error repARN, err := ARNForNewRegion(d.Get("global_table_arn").(string), aws.StringValue(conn.Config.Region)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) } d.SetId(repARN) @@ -206,11 +207,11 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) } if globalRegion == replicaRegion { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) } session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) @@ -236,14 +237,14 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } if result == nil || result.Table == nil { if d.IsNewResource() { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) } - names.LogNotFoundRemoveState(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id()) + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) d.SetId("") return nil } @@ -251,7 +252,7 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { replica, err := filterReplicasByRegion(result.Table.Replicas, replicaRegion) if err := d.Set("global_secondary_index", flattenReplicaGlobalSecondaryIndexes(replica.GlobalSecondaryIndexes)); err != nil { - return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "global_secondary_index", err) + return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "global_secondary_index", err) } d.Set("kms_key_arn", replica.KMSMasterKeyId) @@ -294,14 +295,14 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e } if err != nil { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } if result == nil || result.Table == nil { if d.IsNewResource() { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) } - names.LogNotFoundRemoveState(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id()) + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) d.SetId("") return nil } @@ -313,7 +314,7 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e }) if err != nil && !tfawserr.ErrCodeEquals(err, "UnknownOperationException") { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table", d.Id(), fmt.Errorf("continuous backups: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table", d.Id(), fmt.Errorf("continuous backups: %w", err)) } if pitrOut != nil && pitrOut.ContinuousBackupsDescription != nil && pitrOut.ContinuousBackupsDescription.ContinuousBackupsStatus != nil { @@ -328,20 +329,20 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e tags, err := ListTags(conn, d.Get("arn").(string)) if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "tags", err) + return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags", err) } if d.Get("propagate_tags").(bool) { globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) } session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) @@ -354,7 +355,7 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e globalTags, err := ListTags(conn, d.Get("global_table_arn").(string)) if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return names.Error(names.DynamoDB, names.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) } globalTags = globalTags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) @@ -364,7 +365,7 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e } if err := d.Set("tags_all", tags.Map()); err != nil { - return names.ErrorSetting(names.DynamoDB, "Table Replica", d.Id(), "tags_all", err) + return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags_all", err) } return nil @@ -380,18 +381,18 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error tableName, err := TableNameFromARN(d.Id()) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) } replicaRegion := aws.StringValue(conn.Config.Region) globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) } if globalRegion == replicaRegion { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) } session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) @@ -477,13 +478,13 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) } } if d.HasChange("point_in_time_recovery") { if err := updatePITR(conn, tableName, d.Get("point_in_time_recovery").(bool), aws.StringValue(conn.Config.Region), meta.(*conns.AWSClient).TerraformVersion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) } } @@ -497,14 +498,14 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error tableName, err := TableNameFromARN(d.Id()) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) } replicaRegion := aws.StringValue(conn.Config.Region) globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) } session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index b246a7b1089..de5e815184e 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" tfdynamodb "github.com/hashicorp/terraform-provider-aws/internal/service/dynamodb" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -1600,7 +1601,7 @@ func testAccCheckTableReplicaHasTags(n string, region string, should bool) resou if aws.StringValue(conn.Config.Region) != region { session, err := conns.NewSessionForRegion(&conn.Config, region, terraformVersion) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.ID, err) } conn = dynamodb.New(session) @@ -1609,21 +1610,21 @@ func testAccCheckTableReplicaHasTags(n string, region string, should bool) resou newARN, err := tfdynamodb.ARNForNewRegion(rs.Primary.Attributes["arn"], region) if err != nil { - return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.ID, err) } tags, err := tfdynamodb.ListTags(conn, newARN) if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], err) + return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], err) } if len(tags.Keys()) > 0 && !should { - return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should not have tags but does")) + return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should not have tags but does")) } if len(tags.Keys()) == 0 && should { - return names.Error(names.DynamoDB, names.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should have tags but does not")) + return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should have tags but does not")) } return nil From 0f4122bed8b52ceb2eb6a5418a7399fa66f4d8bd Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 11:15:07 -0400 Subject: [PATCH 04/15] Add new dynamodb resource --- internal/provider/provider.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d47b93e53e7..e8c51c311dc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -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(), From 175f5dd53ab2dffe2aa5f5c1d779ec0b288f2071 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 11:15:42 -0400 Subject: [PATCH 05/15] table: Clean-up --- internal/service/dynamodb/table.go | 57 +++++++++++++++++++----------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/internal/service/dynamodb/table.go b/internal/service/dynamodb/table.go index ad30ffc1392..70f579ddb09 100644 --- a/internal/service/dynamodb/table.go +++ b/internal/service/dynamodb/table.go @@ -279,6 +279,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, @@ -327,6 +342,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": { @@ -354,26 +374,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, - }, }, } } @@ -583,6 +583,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)) @@ -953,7 +965,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) + } } } From 9ceec9ef82ee5b7e46d9c1a8ed17b6525d6afe47 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 11:16:02 -0400 Subject: [PATCH 06/15] table_replica: Add new resource --- internal/service/dynamodb/table_replica.go | 358 +-- .../service/dynamodb/table_replica_test.go | 2784 ++--------------- 2 files changed, 316 insertions(+), 2826 deletions(-) diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go index 34608aa2b0f..258b6457340 100644 --- a/internal/service/dynamodb/table_replica.go +++ b/internal/service/dynamodb/table_replica.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "log" + "strings" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/dynamodb" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" @@ -47,29 +49,14 @@ func ResourceTableReplica() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "global_secondary_index": { // through global table - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - "read_capacity_override": { - Type: schema.TypeInt, - Optional: true, - }, - }, - }, - }, + // global_secondary_index read capacity override can be set but not return by aws atm either through main/replica nor directly "global_table_arn": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: verify.ValidARN, }, - "kms_key_arn": { // through global table + "kms_key_arn": { // through main table Type: schema.TypeString, Optional: true, Computed: true, @@ -80,19 +67,11 @@ func ResourceTableReplica() *schema.Resource { Optional: true, Default: false, }, - "propagate_tags": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "read_capacity_override": { // through global table - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "table_class_override": { // through global table + // read_capacity_override can be set but requires table write_capacity to be autoscaled which is not yet supported in the provider + "table_class_override": { // through main table Type: schema.TypeString, Optional: true, + ForceNew: true, ValidateFunc: validation.StringInSlice(dynamodb.TableClass_Values(), false), }, "tags": tftags.TagsSchema(), // direct to replica @@ -104,40 +83,32 @@ func ResourceTableReplica() *schema.Resource { func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).DynamoDBConn - globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + replicaRegion := aws.StringValue(conn.Config.Region) + + mainRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) } - if globalRegion == aws.StringValue(conn.Config.Region) { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as global table")) + if mainRegion == aws.StringValue(conn.Config.Region) { + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as main table")) } - session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) } - conn = dynamodb.New(session) // now global table region + conn = dynamodb.New(session) // now main table region var replicaInput = &dynamodb.CreateReplicationGroupMemberAction{} - replicaInput.RegionName = conn.Config.Region + replicaInput.RegionName = aws.String(replicaRegion) if v, ok := d.GetOk("kms_key_arn"); ok { replicaInput.KMSMasterKeyId = aws.String(v.(string)) } - if v, ok := d.GetOk("global_secondary_index"); ok && len(v.([]interface{})) > 0 { - replicaInput.GlobalSecondaryIndexes = expandReplicaGlobalSecondaryIndexes(v.([]interface{})) - } - - if v, ok := d.GetOk("read_capacity_override"); ok { - replicaInput.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ - ReadCapacityUnits: aws.Int64(v.(int64)), - } - } - if v, ok := d.GetOk("table_class_override"); ok { replicaInput.TableClassOverride = aws.String(v.(string)) } @@ -186,17 +157,20 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("waiting for replica (%s) creation: %w", meta.(*conns.AWSClient).Region, err) } - repARN, err := ARNForNewRegion(d.Get("global_table_arn").(string), aws.StringValue(conn.Config.Region)) + d.SetId(tableReplicaID(tableName, mainRegion)) + + repARN, err := ARNForNewRegion(d.Get("global_table_arn").(string), replicaRegion) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) } - d.SetId(repARN) - return resourceTableReplicaRead(d, meta) + d.Set("arn", repARN) + + return resourceTableReplicaUpdate(d, meta) } func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { - // handled through global table (main) + // handled through main table (global table) // * global_secondary_index // * kms_key_arn // * read_capacity_override @@ -205,33 +179,38 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { replicaRegion := aws.StringValue(conn.Config.Region) - globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) + tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } - if globalRegion == replicaRegion { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) - } + globalTableARN := arn.ARN{ + AccountID: meta.(*conns.AWSClient).AccountID, + Partition: meta.(*conns.AWSClient).Partition, + Region: mainRegion, + Resource: fmt.Sprintf("table/%s", tableName), + Service: dynamodb.EndpointsID, + }.String() - session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) - if err != nil { - return fmt.Errorf("new session for region (%s): %w", globalRegion, err) - } + d.Set("global_table_arn", globalTableARN) - conn = dynamodb.New(session) // now global table region + if mainRegion == replicaRegion { + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("replica cannot be in same region as main table")) + } - tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) + session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("reading replica (%s): %w", d.Id(), err) + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) } + conn = dynamodb.New(session) // now main table region + result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{ TableName: aws.String(tableName), }) if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { - log.Printf("[WARN] Dynamodb Table Replica (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Dynamodb Table (%s) not found, removing replica from state", d.Id()) d.SetId("") return nil } @@ -249,20 +228,19 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { return nil } - replica, err := filterReplicasByRegion(result.Table.Replicas, replicaRegion) + replica, err := FilterReplicasByRegion(result.Table.Replicas, replicaRegion) + if !d.IsNewResource() && err != nil && err.Error() == "no replicas found" { + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) + d.SetId("") + return nil + } - if err := d.Set("global_secondary_index", flattenReplicaGlobalSecondaryIndexes(replica.GlobalSecondaryIndexes)); err != nil { - return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "global_secondary_index", err) + if err != nil { + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } d.Set("kms_key_arn", replica.KMSMasterKeyId) - if replica.ProvisionedThroughputOverride != nil { - d.Set("read_capacity_override", replica.ProvisionedThroughputOverride.ReadCapacityUnits) - } else { - d.Set("read_capacity_override", nil) - } - if replica.ReplicaTableClassSummary != nil { d.Set("table_class_override", replica.ReplicaTableClassSummary.TableClass) } else { @@ -279,9 +257,9 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e // * tags conn := meta.(*conns.AWSClient).DynamoDBConn - tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) + tableName, _, err := TableReplicaParseID(d.Id()) if err != nil { - return fmt.Errorf("reading replica (%s): %w", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{ @@ -310,15 +288,15 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e d.Set("arn", result.Table.TableArn) pitrOut, err := conn.DescribeContinuousBackups(&dynamodb.DescribeContinuousBackupsInput{ - TableName: aws.String(d.Id()), + TableName: aws.String(tableName), }) if err != nil && !tfawserr.ErrCodeEquals(err, "UnknownOperationException") { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table", d.Id(), fmt.Errorf("continuous backups: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("continuous backups: %w", err)) } - if pitrOut != nil && pitrOut.ContinuousBackupsDescription != nil && pitrOut.ContinuousBackupsDescription.ContinuousBackupsStatus != nil { - d.Set("point_in_time_recovery", aws.StringValue(pitrOut.ContinuousBackupsDescription.ContinuousBackupsStatus) == dynamodb.PointInTimeRecoveryStatusEnabled) + if pitrOut != nil && pitrOut.ContinuousBackupsDescription != nil && pitrOut.ContinuousBackupsDescription.PointInTimeRecoveryDescription != nil { + d.Set("point_in_time_recovery", aws.StringValue(pitrOut.ContinuousBackupsDescription.PointInTimeRecoveryDescription.PointInTimeRecoveryStatus) == dynamodb.PointInTimeRecoveryStatusEnabled) } else { d.Set("point_in_time_recovery", false) } @@ -339,31 +317,6 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags", err) } - if d.Get("propagate_tags").(bool) { - globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) - if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) - } - - session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) - if err != nil { - return fmt.Errorf("new session for region (%s): %w", globalRegion, err) - } - - conn = dynamodb.New(session) // now global table region - - globalTags, err := ListTags(conn, d.Get("global_table_arn").(string)) - - if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) - } - - globalTags = globalTags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - globalTags = globalTags.RemoveDefaultConfig(defaultTagsConfig) - - tags = tags.Merge(globalTags) - } - if err := d.Set("tags_all", tags.Map()); err != nil { return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags_all", err) } @@ -372,75 +325,53 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e } func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error { - // handled through global table (main) + // handled through main table (main) // * global_secondary_index // * kms_key_arn // * read_capacity_override // * table_class_override - conn := meta.(*conns.AWSClient).DynamoDBConn + repConn := meta.(*conns.AWSClient).DynamoDBConn - tableName, err := TableNameFromARN(d.Id()) + tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) } - replicaRegion := aws.StringValue(conn.Config.Region) - - globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) - if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) - } + replicaRegion := aws.StringValue(repConn.Config.Region) - if globalRegion == replicaRegion { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as global table")) + if mainRegion == replicaRegion { + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as main table")) } - session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + session, err := conns.NewSessionForRegion(&repConn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) } - conn = dynamodb.New(session) // now global table region + tabConn := dynamodb.New(session) // now main table region - viaGlobalChanges := false - viaGlobalInput := &dynamodb.UpdateReplicationGroupMemberAction{ + viaMainChanges := false + viaMainInput := &dynamodb.UpdateReplicationGroupMemberAction{ RegionName: aws.String(replicaRegion), } - if d.HasChange("global_secondary_index") { - viaGlobalChanges = true - viaGlobalInput.GlobalSecondaryIndexes = expandReplicaGlobalSecondaryIndexes(d.Get("global_secondary_index").(*schema.Set).List()) - } - if d.HasChange("kms_key_arn") { - viaGlobalChanges = true - viaGlobalInput.KMSMasterKeyId = aws.String(d.Get("kms_key_arn").(string)) + viaMainChanges = true + viaMainInput.KMSMasterKeyId = aws.String(d.Get("kms_key_arn").(string)) } - if d.HasChange("read_capacity_override") { - viaGlobalChanges = true - viaGlobalInput.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ - ReadCapacityUnits: aws.Int64(d.Get("read_capacity_override").(int64)), - } - } - - if d.HasChange("table_class_override") { - viaGlobalChanges = true - viaGlobalInput.TableClassOverride = aws.String(d.Get("table_class_override").(string)) - } - - if viaGlobalChanges { + if viaMainChanges { input := &dynamodb.UpdateTableInput{ ReplicaUpdates: []*dynamodb.ReplicationGroupUpdate{ { - Update: viaGlobalInput, + Update: viaMainInput, }, }, TableName: aws.String(tableName), } err := resource.Retry(maxDuration(replicaUpdateTimeout, d.Timeout(schema.TimeoutUpdate)), func() *resource.RetryError { - _, err := conn.UpdateTable(input) + _, err := tabConn.UpdateTable(input) if err != nil { if tfawserr.ErrCodeEquals(err, "ThrottlingException") { return resource.RetryableError(err) @@ -458,33 +389,37 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error }) if tfresource.TimedOut(err) { - _, err = conn.UpdateTable(input) + _, err = tabConn.UpdateTable(input) } if err != nil && !tfawserr.ErrMessageContains(err, "ValidationException", "no actions specified") { - return fmt.Errorf("creating replica (%s): %w", d.Id(), err) + return fmt.Errorf("updating replica (%s): %w", d.Id(), err) } - if _, err := waitReplicaActive(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("waiting for replica (%s) creation: %w", d.Id(), err) + if _, err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) } } // handled direct to replica // * point_in_time_recovery // * tags - conn = meta.(*conns.AWSClient).DynamoDBConn + if d.HasChanges("point_in_time_recovery", "tags_all") { + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(repConn, d.Get("arn").(string), o, n); err != nil { + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + } + } - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + if d.HasChange("point_in_time_recovery") { + if err := updatePITR(repConn, tableName, d.Get("point_in_time_recovery").(bool), replicaRegion, meta.(*conns.AWSClient).TerraformVersion, d.Timeout(schema.TimeoutUpdate)); err != nil { + return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + } } - } - if d.HasChange("point_in_time_recovery") { - if err := updatePITR(conn, tableName, d.Get("point_in_time_recovery").(bool), aws.StringValue(conn.Config.Region), meta.(*conns.AWSClient).TerraformVersion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + if _, err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) } } @@ -492,28 +427,23 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error } func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*conns.AWSClient).DynamoDBConn - log.Printf("[DEBUG] DynamoDB delete Table Replica: %s", d.Id()) - tableName, err := TableNameFromARN(d.Id()) + tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionDeleting, "Table Replica", d.Id(), err) } - replicaRegion := aws.StringValue(conn.Config.Region) + conn := meta.(*conns.AWSClient).DynamoDBConn - globalRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) - if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) - } + replicaRegion := aws.StringValue(conn.Config.Region) - session, err := conns.NewSessionForRegion(&conn.Config, globalRegion, meta.(*conns.AWSClient).TerraformVersion) + session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", globalRegion, err) + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) } - conn = dynamodb.New(session) // now global table region + conn = dynamodb.New(session) // now main table region input := &dynamodb.UpdateTableInput{ TableName: aws.String(tableName), @@ -559,96 +489,30 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error return nil } -func filterReplicasByRegion(replicas []*dynamodb.ReplicaDescription, region string) (*dynamodb.ReplicaDescription, error) { - if len(replicas) == 0 { - return nil, errors.New("no replicas found") - } - - for _, replica := range replicas { - if aws.StringValue(replica.RegionName) == region { - return replica, nil - } - } - - return nil, errors.New("replica not found") -} - -func expandReplicaGlobalSecondaryIndex(data map[string]interface{}) *dynamodb.ReplicaGlobalSecondaryIndex { - if data == nil { - return nil - } - - idx := &dynamodb.ReplicaGlobalSecondaryIndex{ - IndexName: aws.String(data["name"].(string)), - } - - if v, ok := data["read_capacity_override"].(int64); ok { - idx.ProvisionedThroughputOverride = &dynamodb.ProvisionedThroughputOverride{ - ReadCapacityUnits: aws.Int64(v), - } - } - - return idx -} - -func expandReplicaGlobalSecondaryIndexes(tfList []interface{}) []*dynamodb.ReplicaGlobalSecondaryIndex { - if len(tfList) == 0 { - return nil - } +func TableReplicaParseID(id string) (string, string, error) { + parts := strings.Split(id, ":") - var apiObjects []*dynamodb.ReplicaGlobalSecondaryIndex - - for _, tfMapRaw := range tfList { - tfMap, ok := tfMapRaw.(map[string]interface{}) - - if !ok { - continue - } - - apiObject := expandReplicaGlobalSecondaryIndex(tfMap) - - if apiObject == nil { - continue - } - - apiObjects = append(apiObjects, apiObject) + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil } - return apiObjects + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected table-name:main-table-region", id) } -func flattenReplicaGlobalSecondaryIndex(apiObject *dynamodb.ReplicaGlobalSecondaryIndexDescription) map[string]interface{} { - if apiObject == nil { - return nil - } - - tfMap := map[string]interface{}{} - - if apiObject.IndexName != nil { - tfMap["name"] = aws.StringValue(apiObject.IndexName) - } - - if apiObject.ProvisionedThroughputOverride != nil && apiObject.ProvisionedThroughputOverride.ReadCapacityUnits != nil { - tfMap["read_capacity_override"] = aws.Int64Value(apiObject.ProvisionedThroughputOverride.ReadCapacityUnits) - } - - return tfMap +func tableReplicaID(tableName, mainRegion string) string { + return fmt.Sprintf("%s:%s", tableName, mainRegion) } -func flattenReplicaGlobalSecondaryIndexes(apiObjects []*dynamodb.ReplicaGlobalSecondaryIndexDescription) []interface{} { - if len(apiObjects) == 0 { - return nil +func FilterReplicasByRegion(replicas []*dynamodb.ReplicaDescription, region string) (*dynamodb.ReplicaDescription, error) { + if len(replicas) == 0 { + return nil, errors.New("no replicas found") } - var tfList []interface{} - - for _, apiObject := range apiObjects { - if apiObject == nil { - continue + for _, replica := range replicas { + if aws.StringValue(replica.RegionName) == region { + return replica, nil } - - tfList = append(tfList, flattenReplicaGlobalSecondaryIndex(apiObject)) } - return tfList + return nil, errors.New("replica not found") } diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index de5e815184e..a7f00a65b1d 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "log" - "regexp" "testing" "github.com/aws/aws-sdk-go/aws" @@ -22,30 +21,20 @@ import ( func TestAccDynamoDBTableReplica_basic(t *testing.T) { var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" + resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), CheckDestroy: testAccCheckTableReplicaDestroy, Steps: []resource.TestStep{ { Config: testAccTableReplicaConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "dynamodb", fmt.Sprintf("table/%s", rName)), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "1"), - resource.TestCheckResourceAttr(resourceName, "hash_key", rName), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": rName, - "type": "S", - }), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), - resource.TestCheckResourceAttr(resourceName, "table_class", ""), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), ), }, { @@ -59,386 +48,43 @@ func TestAccDynamoDBTableReplica_basic(t *testing.T) { func TestAccDynamoDBTableReplica_disappears(t *testing.T) { var table1 dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" + resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), CheckDestroy: testAccCheckTableReplicaDestroy, Steps: []resource.TestStep{ { Config: testAccTableReplicaConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table1), - acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTable(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Disappears_payPerRequestWithGSI(t *testing.T) { - var table1, table2 dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table1), - acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTable(), resourceName), + testAccCheckTableReplicaExists(resourceName, &table1), + acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTableReplica(), resourceName), ), ExpectNonEmptyPlan: true, }, - { - Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table2), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_extended(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_initialState(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - testAccCheckInitialTableReplicaConf(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_addSecondaryGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "hash_key", "TestTableHashKey"), - resource.TestCheckResourceAttr(resourceName, "range_key", "TestTableRangeKey"), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "2"), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "2"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), - resource.TestCheckResourceAttr(resourceName, "attribute.#", "4"), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "1"), - resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestTableHashKey", - "type": "S", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestTableRangeKey", - "type": "S", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestLSIRangeKey", - "type": "N", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "ReplacementGSIRangeKey", - "type": "N", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "name": "ReplacementTestTableGSI", - "hash_key": "TestTableHashKey", - "range_key": "ReplacementGSIRangeKey", - "write_capacity": "5", - "read_capacity": "5", - "projection_type": "INCLUDE", - "non_key_attributes.#": "1", - }), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "TestNonKeyAttribute"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ - "name": "TestTableLSI", - "range_key": "TestLSIRangeKey", - "projection_type": "ALL", - }), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_enablePITR(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_initialState(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - testAccCheckInitialTableReplicaConf(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_backup(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "true"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_BillingMode_payPerRequestToProvisioned(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingPayPerRequest(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_billingProvisioned(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_BillingMode_payPerRequestToProvisionedIgnoreChanges(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingPayPerRequest(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_billingProvisionedIgnoreChanges(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "1"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_BillingMode_provisionedToPayPerRequest(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingProvisioned(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_billingPayPerRequest(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_BillingMode_provisionedToPayPerRequestIgnoreChanges(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingProvisioned(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "5"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "5"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_billingPayPerRequestIgnoreChanges(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_BillingModeGSI_payPerRequestToProvisioned(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "0"), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "0"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_billingProvisionedGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - ), - }, }, }) } -func TestAccDynamoDBTableReplica_BillingModeGSI_provisionedToPayPerRequest(t *testing.T) { +func TestAccDynamoDBTableReplica_pitr(t *testing.T) { var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" + resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), CheckDestroy: testAccCheckTableReplicaDestroy, Steps: []resource.TestStep{ { - Config: testAccTableReplicaConfig_billingProvisionedGSI(rName), + Config: testAccTableReplicaConfig_pitr(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery", "true"), ), }, { @@ -446,36 +92,27 @@ func TestAccDynamoDBTableReplica_BillingModeGSI_provisionedToPayPerRequest(t *te ImportState: true, ImportStateVerify: true, }, - { - Config: testAccTableReplicaConfig_billingPayPerRequestGSI(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModePayPerRequest), - ), - }, }, }) } -func TestAccDynamoDBTableReplica_streamSpecification(t *testing.T) { +func TestAccDynamoDBTableReplica_tags(t *testing.T) { var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" + resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), CheckDestroy: testAccCheckTableReplicaDestroy, Steps: []resource.TestStep{ { - Config: testAccTableReplicaConfig_streamSpecification(rName, true, "KEYS_ONLY"), + Config: testAccTableReplicaConfig_tags1(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "stream_enabled", "true"), - resource.TestCheckResourceAttr(resourceName, "stream_view_type", "KEYS_ONLY"), - acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))), - resource.TestCheckResourceAttrSet(resourceName, "stream_label"), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.tape", "Valladolid"), ), }, { @@ -484,51 +121,15 @@ func TestAccDynamoDBTableReplica_streamSpecification(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccTableReplicaConfig_streamSpecification(rName, false, ""), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "stream_enabled", "false"), - resource.TestCheckResourceAttr(resourceName, "stream_view_type", ""), - acctest.MatchResourceAttrRegionalARN(resourceName, "stream_arn", "dynamodb", regexp.MustCompile(fmt.Sprintf("table/%s/stream", rName))), - resource.TestCheckResourceAttrSet(resourceName, "stream_label"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_streamSpecificationValidation(t *testing.T) { - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_streamSpecification("anything", true, ""), - ExpectError: regexp.MustCompile(`stream_view_type is required when stream_enabled = true`), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_tags(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_tags(rName), + Config: testAccTableReplicaConfig_tags2(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - testAccCheckInitialTableReplicaConf(resourceName), - resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "5"), + resource.TestCheckResourceAttr(resourceName, "tags.arise", "Melandru"), + resource.TestCheckResourceAttr(resourceName, "tags.brightest", "Lights"), + resource.TestCheckResourceAttr(resourceName, "tags.shooting", "Stars"), + resource.TestCheckResourceAttr(resourceName, "tags.tape", "Valladolid"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -536,42 +137,12 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - }, - }) -} - -// https://github.com/hashicorp/terraform/issues/13243 -func TestAccDynamoDBTableReplica_gsiUpdateCapacity(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ { - Config: testAccTableReplicaConfig_gsiUpdate(rName), + Config: testAccTableReplicaConfig_tags3(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "1", - "write_capacity": "1", - "name": "att1-index", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "1", - "write_capacity": "1", - "name": "att2-index", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "1", - "write_capacity": "1", - "name": "att3-index", - }), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), }, { @@ -579,1926 +150,144 @@ func TestAccDynamoDBTableReplica_gsiUpdateCapacity(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - { - Config: testAccTableReplicaConfig_gsiUpdatedCapacity(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "2", - "write_capacity": "2", - "name": "att1-index", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "2", - "write_capacity": "2", - "name": "att2-index", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "read_capacity": "2", - "write_capacity": "2", - "name": "att3-index", - }), - ), - }, }, }) } -func TestAccDynamoDBTableReplica_gsiUpdateOtherAttributes(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - +func TestAccDynamoDBTableReplica_tableClass(t *testing.T) { var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" + resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), CheckDestroy: testAccCheckTableReplicaDestroy, Steps: []resource.TestStep{ { - Config: testAccTableReplicaConfig_gsiUpdate(rName), + Config: testAccTableReplicaConfig_tableClass(rName, "STANDARD"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att3", - "name": "att3-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att1", - "name": "att1-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att2", - "name": "att2-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "table_class_override", "STANDARD"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att4", - "name": "att2-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "att2", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att3", - "name": "att3-index", - "non_key_attributes.#": "1", - "projection_type": "INCLUDE", - "range_key": "att4", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att1", - "name": "att1-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), - ), - }, - }, - }) -} - -// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/15115 -func TestAccDynamoDBTableReplica_lsiNonKeyAttributes(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_lsiNonKeyAttributes(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ - "name": "TestTableLSI", - "non_key_attributes.#": "1", - "non_key_attributes.0": "TestNonKeyAttribute", - "projection_type": "INCLUDE", - "range_key": "TestLSIRangeKey", - }), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -// https://github.com/hashicorp/terraform-provider-aws/issues/566 -func TestAccDynamoDBTableReplica_gsiUpdateNonKeyAttributes(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "3"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att4", - "name": "att2-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "att2", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att3", - "name": "att3-index", - "non_key_attributes.#": "1", - "projection_type": "INCLUDE", - "range_key": "att4", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att1", - "name": "att1-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_gsiUpdatedNonKeyAttributes(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att4", - "name": "att2-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "att2", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att3", - "name": "att3-index", - "non_key_attributes.#": "2", - "projection_type": "INCLUDE", - "range_key": "att4", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "AnotherAttribute"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att1", - "name": "att1-index", - "non_key_attributes.#": "0", - "projection_type": "ALL", - "range_key": "", - "read_capacity": "1", - "write_capacity": "1", - }), - ), - }, - }, - }) -} - -// https://github.com/hashicorp/terraform-provider-aws/issues/671 -func TestAccDynamoDBTableReplica_GsiUpdateNonKeyAttributes_emptyPlan(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - attributes := fmt.Sprintf("%q, %q", "AnotherAttribute", "RandomAttribute") - reorderedAttributes := fmt.Sprintf("%q, %q", "RandomAttribute", "AnotherAttribute") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, attributes), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "hash_key": "att1", - "name": "att1-index", - "non_key_attributes.#": "2", - "projection_type": "INCLUDE", - "range_key": "att2", - "read_capacity": "1", - "write_capacity": "1", - }), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "AnotherAttribute"), - resource.TestCheckTypeSetElemAttr(resourceName, "global_secondary_index.*.non_key_attributes.*", "RandomAttribute"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, reorderedAttributes), - 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 TestAccDynamoDBTableReplica_TTL_enabled(t *testing.T) { - var table dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_timeToLive(rName, true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "true"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -// 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 TestAccDynamoDBTableReplica_TTL_disabled(t *testing.T) { - var table dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_timeToLive(rName, false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "false"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_timeToLive(rName, true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "ttl.#", "1"), - resource.TestCheckResourceAttr(resourceName, "ttl.0.enabled", "true"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_attributeUpdate(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "S"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { // Attribute type change - Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "N"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - { // New attribute addition (index update) - Config: testAccTableReplicaConfig_twoAttributes(rName, "firstKey", "secondKey", "firstKey", "N", "secondKey", "S"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - { // Attribute removal (index update) - Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "firstKey", "S"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_lsiUpdate(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_lsi(rName, "lsi-original"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { // Change name of local secondary index - Config: testAccTableReplicaConfig_lsi(rName, "lsi-changed"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_attributeUpdateValidation(t *testing.T) { - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_oneAttribute(rName, "firstKey", "unusedKey", "S"), - ExpectError: regexp.MustCompile(`attributes must be indexed. Unused attributes: \["unusedKey"\]`), - }, - { - Config: testAccTableReplicaConfig_twoAttributes(rName, "firstKey", "secondKey", "firstUnused", "N", "secondUnused", "S"), - ExpectError: regexp.MustCompile(`attributes must be indexed. Unused attributes: \["firstUnused"\ \"secondUnused\"]`), - }, - { - Config: testAccTableReplicaConfig_unmatchedIndexes(rName, "firstUnused", "secondUnused"), - ExpectError: regexp.MustCompile(`indexes must match a defined attribute. Unmatched indexes:`), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_encryption(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var confBYOK, confEncEnabled, confEncDisabled dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - kmsKeyResourceName := "aws_kms_key.test" - kmsAliasDatasourceName := "data.aws_kms_alias.dynamodb" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_initialStateEncryptionBYOK(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), - resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName, false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &confEncDisabled), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), - func(s *terraform.State) error { - if !confEncDisabled.Table.CreationDateTime.Equal(*confBYOK.Table.CreationDateTime) { - return fmt.Errorf("DynamoDB table recreated when changing SSE") - } - return nil - }, - ), - }, - { - Config: testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName, true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &confEncEnabled), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), - resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsAliasDatasourceName, "target_key_arn"), - func(s *terraform.State) error { - if !confEncEnabled.Table.CreationDateTime.Equal(*confEncDisabled.Table.CreationDateTime) { - return fmt.Errorf("DynamoDB table recreated when changing SSE") - } - return nil - }, - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_multiple(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var table dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 3) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replica2(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - ), - }, - { - Config: testAccTableReplicaConfig_replica2(rName), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_replica0(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "replica.#", "0"), - ), - }, - { - Config: testAccTableReplicaConfig_replica2(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_single(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replica1(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), - ), - }, - { - Config: testAccTableReplicaConfig_replica1(rName), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_replica0(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "0"), - ), - }, - { - Config: testAccTableReplicaConfig_replica1(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_tagMagic(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replicaMoreTags(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), - ), - }, - { - Config: testAccTableReplicaConfig_replicaMoreTags(rName), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_singleWithCMK(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - kmsKeyResourceName := "aws_kms_key.test" - // kmsAliasDatasourceName := "data.aws_kms_alias.master" - kmsKeyReplicaResourceName := "aws_kms_key.alt_test" - // kmsAliasReplicaDatasourceName := "data.aws_kms_alias.replica" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replicaCMK(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), - resource.TestCheckResourceAttrPair(resourceName, "replica.0.kms_key_arn", kmsKeyReplicaResourceName, "arn"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), - resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_pitr(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replicaPITR(rName, false, true, false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "point_in_time_recovery": "true", - "region_name": acctest.AlternateRegion(), - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "point_in_time_recovery": "false", - "region_name": acctest.ThirdRegion(), - }), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "false"), - ), - }, - { - Config: testAccTableReplicaConfig_replicaPITR(rName, true, false, true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "point_in_time_recovery": "false", - "region_name": acctest.AlternateRegion(), - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "point_in_time_recovery": "true", - "region_name": acctest.ThirdRegion(), - }), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.#", "1"), - resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery.0.enabled", "true"), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_tagsOneOfTwo(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replicaTags(rName, "benny", "smiles", true, false), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - testAccCheckTableReplicaHasTags(resourceName, acctest.AlternateRegion(), true), - testAccCheckTableReplicaHasTags(resourceName, acctest.ThirdRegion(), false), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "region_name": acctest.AlternateRegion(), - "propagate_tags": "true", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "region_name": acctest.ThirdRegion(), - "propagate_tags": "false", - }), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_Replica_tagsTwoOfTwo(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_replicaTags(rName, "Structure", "Adobe", true, true), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &conf), - testAccCheckTableReplicaHasTags(resourceName, acctest.AlternateRegion(), true), - testAccCheckTableReplicaHasTags(resourceName, acctest.ThirdRegion(), true), - resource.TestCheckResourceAttr(resourceName, "replica.#", "2"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "region_name": acctest.AlternateRegion(), - "propagate_tags": "true", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "replica.*", map[string]string{ - "region_name": acctest.ThirdRegion(), - "propagate_tags": "true", - }), - ), - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_tableClassInfrequentAccess(t *testing.T) { - var table dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_class(rName, "STANDARD_INFREQUENT_ACCESS"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "table_class", "STANDARD_INFREQUENT_ACCESS"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccTableReplicaConfig_class(rName, "STANDARD"), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &table), - resource.TestCheckResourceAttr(resourceName, "table_class", "STANDARD"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_backupEncryption(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var confBYOK dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - kmsKeyResourceName := "aws_kms_key.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_backupInitialStateEncryption(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), - resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "restore_to_latest_time", - "restore_date_time", - "restore_source_name", - }, - }, - }, - }) -} - -func TestAccDynamoDBTableReplica_backup_overrideEncryption(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var confBYOK dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - kmsKeyResourceName := "aws_kms_key.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTableReplicaDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableReplicaConfig_backupInitialStateOverrideEncryption(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableReplicaExists(resourceName, &confBYOK), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "1"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.0.enabled", "true"), - resource.TestCheckResourceAttrPair(resourceName, "server_side_encryption.0.kms_key_arn", kmsKeyResourceName, "arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "restore_to_latest_time", - "restore_date_time", - "restore_source_name", - }, - }, - }, - }) -} - -func testAccCheckTableReplicaDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_dynamodb_table" { - continue - } - - log.Printf("[DEBUG] Checking if DynamoDB table %s exists", rs.Primary.ID) - // Check if queue exists by checking for its attributes - params := &dynamodb.DescribeTableInput{ - TableName: aws.String(rs.Primary.ID), - } - - _, err := conn.DescribeTable(params) - if err == nil { - return fmt.Errorf("DynamoDB table %s still exists. Failing!", rs.Primary.ID) - } - - if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { - continue - } - - return err - } - - return nil -} - -func testAccCheckInitialTableReplicaExists(n string, table *dynamodb.DescribeTableOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - log.Printf("[DEBUG] Trying to create initial table state!") - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No DynamoDB table name specified!") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn - - params := &dynamodb.DescribeTableInput{ - TableName: aws.String(rs.Primary.ID), - } - - resp, err := conn.DescribeTable(params) - - if err != nil { - return fmt.Errorf("Problem describing table '%s': %s", rs.Primary.ID, err) - } - - *table = *resp - - return nil - } -} - -func testAccCheckTableReplicaHasTags(n string, region string, should bool) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No DynamoDB table name specified!") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn - terraformVersion := acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion - - if aws.StringValue(conn.Config.Region) != region { - session, err := conns.NewSessionForRegion(&conn.Config, region, terraformVersion) - if err != nil { - return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.ID, err) - } - - conn = dynamodb.New(session) - } - - newARN, err := tfdynamodb.ARNForNewRegion(rs.Primary.Attributes["arn"], region) - - if err != nil { - return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.ID, err) - } - - tags, err := tfdynamodb.ListTags(conn, newARN) - - if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], err) - } - - if len(tags.Keys()) > 0 && !should { - return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should not have tags but does")) - } - - if len(tags.Keys()) == 0 && should { - return create.Error(names.DynamoDB, create.ErrActionChecking, "Table", rs.Primary.Attributes["arn"], errors.New("replica should have tags but does not")) - } - - return nil - } -} - -func testAccCheckInitialTableReplicaConf(resourceName string) resource.TestCheckFunc { - return resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "hash_key", "TestTableHashKey"), - resource.TestCheckResourceAttr(resourceName, "range_key", "TestTableRangeKey"), - resource.TestCheckResourceAttr(resourceName, "billing_mode", dynamodb.BillingModeProvisioned), - resource.TestCheckResourceAttr(resourceName, "write_capacity", "2"), - resource.TestCheckResourceAttr(resourceName, "read_capacity", "1"), - resource.TestCheckResourceAttr(resourceName, "server_side_encryption.#", "0"), - resource.TestCheckResourceAttr(resourceName, "attribute.#", "4"), - resource.TestCheckResourceAttr(resourceName, "global_secondary_index.#", "1"), - resource.TestCheckResourceAttr(resourceName, "local_secondary_index.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestTableHashKey", - "type": "S", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestTableRangeKey", - "type": "S", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestLSIRangeKey", - "type": "N", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "attribute.*", map[string]string{ - "name": "TestGSIRangeKey", - "type": "S", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "global_secondary_index.*", map[string]string{ - "name": "InitialTestTableGSI", - "hash_key": "TestTableHashKey", - "range_key": "TestGSIRangeKey", - "write_capacity": "1", - "read_capacity": "1", - "projection_type": "KEYS_ONLY", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "local_secondary_index.*", map[string]string{ - "name": "TestTableLSI", - "range_key": "TestLSIRangeKey", - "projection_type": "ALL", - }), - ) -} - -func testAccTableReplicaConfig_basic(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 1 - hash_key = %[1]q - - attribute { - name = %[1]q - type = "S" - } -} -`, rName) -} - -func testAccTableReplicaConfig_backup(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 1 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - point_in_time_recovery { - enabled = true - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingPayPerRequest(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - billing_mode = "PAY_PER_REQUEST" - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingPayPerRequestIgnoreChanges(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - billing_mode = "PAY_PER_REQUEST" - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - lifecycle { - ignore_changes = [read_capacity, write_capacity] - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingProvisioned(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - billing_mode = "PROVISIONED" - hash_key = "TestTableHashKey" - - read_capacity = 5 - write_capacity = 5 - - attribute { - name = "TestTableHashKey" - type = "S" - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingProvisionedIgnoreChanges(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - billing_mode = "PROVISIONED" - hash_key = "TestTableHashKey" - - read_capacity = 5 - write_capacity = 5 - - attribute { - name = "TestTableHashKey" - type = "S" - } - - lifecycle { - ignore_changes = [read_capacity, write_capacity] - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingPayPerRequestGSI(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - billing_mode = "PAY_PER_REQUEST" - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - attribute { - name = "TestTableGSIKey" - type = "S" - } - - global_secondary_index { - name = "TestTableGSI" - hash_key = "TestTableGSIKey" - projection_type = "KEYS_ONLY" - } -} -`, rName) -} - -func testAccTableReplicaConfig_billingProvisionedGSI(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - billing_mode = "PROVISIONED" - hash_key = "TestTableHashKey" - name = %[1]q - read_capacity = 1 - write_capacity = 1 - - attribute { - name = "TestTableHashKey" - type = "S" - } - - attribute { - name = "TestTableGSIKey" - type = "S" - } - - global_secondary_index { - hash_key = "TestTableGSIKey" - name = "TestTableGSI" - projection_type = "KEYS_ONLY" - read_capacity = 1 - write_capacity = 1 - } -} -`, rName) -} - -func testAccTableReplicaConfig_initialState(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 2 - hash_key = "TestTableHashKey" - range_key = "TestTableRangeKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - attribute { - name = "TestTableRangeKey" - type = "S" - } - - attribute { - name = "TestLSIRangeKey" - type = "N" - } - - attribute { - name = "TestGSIRangeKey" - type = "S" - } - - local_secondary_index { - name = "TestTableLSI" - range_key = "TestLSIRangeKey" - projection_type = "ALL" - } - - global_secondary_index { - name = "InitialTestTableGSI" - hash_key = "TestTableHashKey" - range_key = "TestGSIRangeKey" - write_capacity = 1 - read_capacity = 1 - projection_type = "KEYS_ONLY" - } -} -`, rName) -} - -func testAccTableReplicaConfig_initialStateEncryptionAmazonCMK(rName string, enabled bool) string { - return fmt.Sprintf(` -data "aws_kms_alias" "dynamodb" { - name = "alias/aws/dynamodb" -} - -resource "aws_kms_key" "test" { - description = %[1]q -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 1 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - server_side_encryption { - enabled = %[2]t - } -} -`, rName, enabled) -} - -func testAccTableReplicaConfig_initialStateEncryptionBYOK(rName string) string { - return fmt.Sprintf(` -resource "aws_kms_key" "test" { - description = %[1]q -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 2 - write_capacity = 2 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - server_side_encryption { - enabled = true - kms_key_arn = aws_kms_key.test.arn - } -} -`, rName) -} - -func testAccTableReplicaConfig_addSecondaryGSI(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 2 - write_capacity = 2 - hash_key = "TestTableHashKey" - range_key = "TestTableRangeKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - attribute { - name = "TestTableRangeKey" - type = "S" - } - - attribute { - name = "TestLSIRangeKey" - type = "N" - } - - attribute { - name = "ReplacementGSIRangeKey" - type = "N" - } - - local_secondary_index { - name = "TestTableLSI" - range_key = "TestLSIRangeKey" - projection_type = "ALL" - } - - global_secondary_index { - name = "ReplacementTestTableGSI" - hash_key = "TestTableHashKey" - range_key = "ReplacementGSIRangeKey" - write_capacity = 5 - read_capacity = 5 - projection_type = "INCLUDE" - non_key_attributes = ["TestNonKeyAttribute"] - } -} -`, rName) -} - -func testAccTableReplicaConfig_streamSpecification(rName string, enabled bool, viewType string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 2 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - stream_enabled = %[2]t - stream_view_type = %[3]q -} -`, rName, enabled, viewType) -} - -func testAccTableReplicaConfig_tags(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 2 - hash_key = "TestTableHashKey" - range_key = "TestTableRangeKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - attribute { - name = "TestTableRangeKey" - type = "S" - } - - attribute { - name = "TestLSIRangeKey" - type = "N" - } - - attribute { - name = "TestGSIRangeKey" - type = "S" - } - - local_secondary_index { - name = "TestTableLSI" - range_key = "TestLSIRangeKey" - projection_type = "ALL" - } - - global_secondary_index { - name = "InitialTestTableGSI" - hash_key = "TestTableHashKey" - range_key = "TestGSIRangeKey" - write_capacity = 1 - read_capacity = 1 - projection_type = "KEYS_ONLY" - } - - tags = { - Name = %[1]q - AccTest = "yes" - Testing = "absolutely" - } -} -`, rName) -} - -func testAccTableReplicaConfig_gsiUpdate(rName string) string { - return fmt.Sprintf(` -variable "capacity" { - default = 1 -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = var.capacity - write_capacity = var.capacity - hash_key = "id" - - attribute { - name = "id" - type = "S" - } - - attribute { - name = "att1" - type = "S" - } - - attribute { - name = "att2" - type = "S" - } - - attribute { - name = "att3" - type = "S" - } - - global_secondary_index { - name = "att1-index" - hash_key = "att1" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att2-index" - hash_key = "att2" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att3-index" - hash_key = "att3" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } -} -`, rName) -} - -func testAccTableReplicaConfig_gsiUpdatedCapacity(rName string) string { - return fmt.Sprintf(` -variable "capacity" { - default = 2 -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = var.capacity - write_capacity = var.capacity - hash_key = "id" - - attribute { - name = "id" - type = "S" - } - - attribute { - name = "att1" - type = "S" - } - - attribute { - name = "att2" - type = "S" - } - - attribute { - name = "att3" - type = "S" - } - - global_secondary_index { - name = "att1-index" - hash_key = "att1" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att2-index" - hash_key = "att2" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att3-index" - hash_key = "att3" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } -} -`, rName) -} - -func testAccTableReplicaConfig_gsiUpdatedOtherAttributes(rName string) string { - return fmt.Sprintf(` -variable "capacity" { - default = 1 -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = var.capacity - write_capacity = var.capacity - hash_key = "id" - - attribute { - name = "id" - type = "S" - } - - attribute { - name = "att1" - type = "S" - } - - attribute { - name = "att2" - type = "S" - } - - attribute { - name = "att3" - type = "S" - } - - attribute { - name = "att4" - type = "S" - } - - global_secondary_index { - name = "att1-index" - hash_key = "att1" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att2-index" - hash_key = "att4" - range_key = "att2" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att3-index" - hash_key = "att3" - range_key = "att4" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "INCLUDE" - non_key_attributes = ["RandomAttribute"] - } -} -`, rName) -} - -func testAccTableReplicaConfig_gsiUpdatedNonKeyAttributes(rName string) string { - return fmt.Sprintf(` -variable "capacity" { - default = 1 -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = var.capacity - write_capacity = var.capacity - hash_key = "id" - - attribute { - name = "id" - type = "S" - } - - attribute { - name = "att1" - type = "S" - } - - attribute { - name = "att2" - type = "S" - } - - attribute { - name = "att3" - type = "S" - } - - attribute { - name = "att4" - type = "S" - } - - global_secondary_index { - name = "att1-index" - hash_key = "att1" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att2-index" - hash_key = "att4" - range_key = "att2" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "ALL" - } - - global_secondary_index { - name = "att3-index" - hash_key = "att3" - range_key = "att4" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "INCLUDE" - non_key_attributes = ["RandomAttribute", "AnotherAttribute"] - } -} -`, rName) -} - -func testAccTableReplicaConfig_gsiMultipleNonKeyAttributes(rName, attributes string) string { - return fmt.Sprintf(` -variable "capacity" { - default = 1 + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccTableReplicaConfig_tableClass(rName, "STANDARD_INFREQUENT_ACCESS"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTableReplicaExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "table_class_override", "STANDARD_INFREQUENT_ACCESS"), + ), + }, + }, + }) } -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = var.capacity - write_capacity = var.capacity - hash_key = "id" - - attribute { - name = "id" - type = "S" - } - - attribute { - name = "att1" - type = "S" - } +func testAccCheckTableReplicaDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn + replicaRegion := aws.StringValue(conn.Config.Region) - attribute { - name = "att2" - type = "S" - } + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_dynamodb_table_replica" { + continue + } - global_secondary_index { - name = "att1-index" - hash_key = "att1" - range_key = "att2" - write_capacity = var.capacity - read_capacity = var.capacity - projection_type = "INCLUDE" - non_key_attributes = [%s] - } -} -`, rName, attributes) -} + log.Printf("[DEBUG] Checking if DynamoDB table replica %s was destroyed", rs.Primary.ID) -func testAccTableReplicaConfig_lsiNonKeyAttributes(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - hash_key = "TestTableHashKey" - range_key = "TestTableRangeKey" - write_capacity = 1 - read_capacity = 1 + if rs.Primary.ID == "" { + return fmt.Errorf("table replica has no ID!") + } - attribute { - name = "TestTableHashKey" - type = "S" - } + tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) + if err != nil { + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", rs.Primary.ID, err) + } - attribute { - name = "TestTableRangeKey" - type = "S" - } + session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + } - attribute { - name = "TestLSIRangeKey" - type = "N" - } + conn = dynamodb.New(session) // now global table region - local_secondary_index { - name = "TestTableLSI" - range_key = "TestLSIRangeKey" - projection_type = "INCLUDE" - non_key_attributes = ["TestNonKeyAttribute"] - } -} -`, rName) -} + params := &dynamodb.DescribeTableInput{ + TableName: aws.String(tableName), + } -func testAccTableReplicaConfig_timeToLive(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 + result, err := conn.DescribeTable(params) - attribute { - name = "TestTableHashKey" - type = "S" - } + if tfawserr.ErrCodeEquals(err, dynamodb.ErrCodeResourceNotFoundException) { + continue + } - ttl { - attribute_name = %[2]t ? "TestTTL" : "" - enabled = %[2]t - } -} -`, rName, ttlEnabled) -} + if err != nil { + return fmt.Errorf("checking table replica existence %s still exists: %s", rs.Primary.ID, err) + } -func testAccTableReplicaConfig_oneAttribute(rName, hashKey, attrName, attrType string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 10 - write_capacity = 10 - hash_key = "staticHashKey" + if result == nil || result.Table == nil { + continue + } - attribute { - name = "staticHashKey" - type = "S" - } + if _, err := tfdynamodb.FilterReplicasByRegion(result.Table.Replicas, replicaRegion); err == nil { + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, errors.New("still exists")) + } - attribute { - name = %[3]q - type = %[4]q - } + return err + } - global_secondary_index { - name = "gsiName" - hash_key = %[2]q - write_capacity = 10 - read_capacity = 10 - projection_type = "KEYS_ONLY" - } -} -`, rName, hashKey, attrName, attrType) + return nil } -func testAccTableReplicaConfig_twoAttributes(rName, hashKey, rangeKey, attrName1, attrType1, attrName2, attrType2 string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 10 - write_capacity = 10 - hash_key = "staticHashKey" +func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + log.Printf("[DEBUG] Trying to create initial table replica state!") + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } - attribute { - name = "staticHashKey" - type = "S" - } + if rs.Primary.ID == "" { + return fmt.Errorf("table replica has no ID!") + } - attribute { - name = %[4]q - type = %[5]q - } + conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn - attribute { - name = %[6]q - type = %[7]q - } + tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) + if err != nil { + return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", rs.Primary.ID, err) + } - global_secondary_index { - name = "gsiName" - hash_key = %[2]q - range_key = %[3]q - write_capacity = 10 - read_capacity = 10 - projection_type = "KEYS_ONLY" - } -} -`, rName, hashKey, rangeKey, attrName1, attrType1, attrName2, attrType2) -} + session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) + if err != nil { + return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + } -func testAccTableReplicaConfig_unmatchedIndexes(rName, attr1, attr2 string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 10 - write_capacity = 10 - hash_key = "staticHashKey" - range_key = %[2]q + conn = dynamodb.New(session) // now global table region - attribute { - name = "staticHashKey" - type = "S" - } + params := &dynamodb.DescribeTableInput{ + TableName: aws.String(tableName), + } - local_secondary_index { - name = "lsiName" - range_key = %[3]q - projection_type = "KEYS_ONLY" - } -} -`, rName, attr1, attr2) + _, err = conn.DescribeTable(params) + if err != nil { + return fmt.Errorf("describing table replica (%s): %s", rs.Primary.ID, err) + } + + return nil + } } -func testAccTableReplicaConfig_replica0(rName string) string { +func testAccTableReplicaConfig_basic(rName string) string { return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` resource "aws_dynamodb_table" "test" { + provider = "awsalternate" name = %[1]q hash_key = "TestTableHashKey" billing_mode = "PAY_PER_REQUEST" @@ -2509,45 +298,27 @@ resource "aws_dynamodb_table" "test" { name = "TestTableHashKey" type = "S" } -} -`, rName)) -} -func testAccTableReplicaConfig_replica1(rName string) string { - return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors - fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" + lifecycle { + ignore_changes = [replica] + } } -resource "aws_dynamodb_table" "test" { - name = %[1]q - hash_key = "TestTableHashKey" - billing_mode = "PAY_PER_REQUEST" - stream_enabled = true - stream_view_type = "NEW_AND_OLD_IMAGES" - - attribute { - name = "TestTableHashKey" - type = "S" - } +resource "aws_dynamodb_table_replica" "test" { + global_table_arn = aws_dynamodb_table.test.arn - replica { - region_name = data.aws_region.alternate.name + tags = { + Name = %[1]q + Pozo = "Amargo" } } `, rName)) } -func testAccTableReplicaConfig_replicaMoreTags(rName string) string { +func testAccTableReplicaConfig_pitr(rName string) string { return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - resource "aws_dynamodb_table" "test" { name = %[1]q hash_key = "TestTableHashKey" @@ -2560,40 +331,23 @@ resource "aws_dynamodb_table" "test" { type = "S" } - replica { - region_name = data.aws_region.alternate.name - propagate_tags = true - - tags = { - ReplicaTag = "Bob" - } + lifecycle { + ignore_changes = [replica] } +} - tags = { - Name = %[1]q - Pozo = "Amargo" - } +resource "aws_dynamodb_table_replica" "test" { + provider = "awsalternate" + global_table_arn = aws_dynamodb_table.test.arn + point_in_time_recovery = true } `, rName)) } -func testAccTableReplicaConfig_replicaCMK(rName string) string { +func testAccTableReplicaConfig_tags1(rName string) string { return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - -resource "aws_kms_key" "test" { - description = %[1]q -} - -resource "aws_kms_key" "alt_test" { - provider = "awsalternate" - description = "%[1]s-2" -} - resource "aws_dynamodb_table" "test" { name = %[1]q hash_key = "TestTableHashKey" @@ -2606,37 +360,31 @@ resource "aws_dynamodb_table" "test" { type = "S" } - replica { - region_name = data.aws_region.alternate.name - kms_key_arn = aws_kms_key.alt_test.arn + tags = { + Name = %[1]q } - server_side_encryption { - enabled = true - kms_key_arn = aws_kms_key.test.arn + lifecycle { + ignore_changes = [replica] } +} + +resource "aws_dynamodb_table_replica" "test" { + provider = "awsalternate" + global_table_arn = aws_dynamodb_table.test.arn - timeouts { - create = "20m" - update = "20m" - delete = "20m" + tags = { + Name = %[1]q + tape = "Valladolid" } } `, rName)) } -func testAccTableReplicaConfig_replicaPITR(rName string, mainPITR, replica1, replica2 bool) string { +func testAccTableReplicaConfig_tags2(rName string) string { return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors + acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - -data "aws_region" "third" { - provider = "awsthird" -} - resource "aws_dynamodb_table" "test" { name = %[1]q hash_key = "TestTableHashKey" @@ -2649,35 +397,34 @@ resource "aws_dynamodb_table" "test" { type = "S" } - point_in_time_recovery { - enabled = %[2]t + tags = { + Name = %[1]q } - replica { - region_name = data.aws_region.alternate.name - point_in_time_recovery = %[3]t + lifecycle { + ignore_changes = [replica] } +} + +resource "aws_dynamodb_table_replica" "test" { + provider = "awsalternate" + global_table_arn = aws_dynamodb_table.test.arn - replica { - region_name = data.aws_region.third.name - point_in_time_recovery = %[4]t + tags = { + Name = %[1]q + tape = "Valladolid" + brightest = "Lights" + arise = "Melandru" + shooting = "Stars" } } -`, rName, mainPITR, replica1, replica2)) +`, rName)) } -func testAccTableReplicaConfig_replica2(rName string) string { +func testAccTableReplicaConfig_tags3(rName string) string { return acctest.ConfigCompose( acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - -data "aws_region" "third" { - provider = "awsthird" -} - resource "aws_dynamodb_table" "test" { name = %[1]q hash_key = "TestTableHashKey" @@ -2690,180 +437,59 @@ resource "aws_dynamodb_table" "test" { type = "S" } - replica { - region_name = data.aws_region.alternate.name + tags = { + Name = %[1]q + } + + lifecycle { + ignore_changes = [replica] } +} - replica { - region_name = data.aws_region.third.name +resource "aws_dynamodb_table_replica" "test" { + provider = "awsalternate" + global_table_arn = aws_dynamodb_table.test.arn + + tags = { + Name = %[1]q } } `, rName)) } -func testAccTableReplicaConfig_replicaTags(rName, key, value string, propagate1, propagate2 bool) string { +func testAccTableReplicaConfig_tableClass(rName, class string) string { return acctest.ConfigCompose( acctest.ConfigMultipleRegionProvider(3), fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - -data "aws_region" "third" { - provider = "awsthird" -} - resource "aws_dynamodb_table" "test" { + provider = "awsalternate" name = %[1]q - hash_key = "TestTableHashKey" + hash_key = "ArticLake" billing_mode = "PAY_PER_REQUEST" stream_enabled = true stream_view_type = "NEW_AND_OLD_IMAGES" attribute { - name = "TestTableHashKey" + name = "ArticLake" type = "S" } - replica { - region_name = data.aws_region.alternate.name - propagate_tags = %[4]t - } - - replica { - region_name = data.aws_region.third.name - propagate_tags = %[5]t - } - tags = { - Name = %[1]q - Pozo = "Amargo" - %[2]s = %[3]q - } -} -`, rName, key, value, propagate1, propagate2)) -} - -func testAccTableReplicaConfig_lsi(rName, lsiName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 10 - write_capacity = 10 - hash_key = "staticHashKey" - range_key = "staticRangeKey" - - attribute { - name = "staticHashKey" - type = "S" - } - - attribute { - name = "staticRangeKey" - type = "S" - } - - attribute { - name = "staticLSIRangeKey" - type = "S" - } - - local_secondary_index { - name = %[2]q - range_key = "staticLSIRangeKey" - projection_type = "KEYS_ONLY" - } -} -`, rName, lsiName) -} - -func testAccTableReplicaConfig_class(rName, tableClass string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "test" { - hash_key = "TestTableHashKey" - name = %[1]q - read_capacity = 1 - write_capacity = 1 - table_class = %[2]q - - attribute { - name = "TestTableHashKey" - type = "S" - } -} -`, rName, tableClass) -} - -func testAccTableReplicaConfig_backupInitialStateOverrideEncryption(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "source" { - name = "%[1]s-source" - read_capacity = 2 - write_capacity = 2 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - point_in_time_recovery { - enabled = true - } - - server_side_encryption { - enabled = false + Name = %[1]q } -} - -resource "aws_kms_key" "test" { - description = %[1]q -} - -resource "aws_dynamodb_table" "test" { - name = "%[1]s-target" - restore_source_name = aws_dynamodb_table.source.name - restore_to_latest_time = true - server_side_encryption { - enabled = true - kms_key_arn = aws_kms_key.test.arn + lifecycle { + ignore_changes = [replica] } } -`, rName) -} - -func testAccTableReplicaConfig_backupInitialStateEncryption(rName string) string { - return fmt.Sprintf(` -resource "aws_dynamodb_table" "source" { - name = "%[1]s-source" - read_capacity = 2 - write_capacity = 2 - hash_key = "TestTableHashKey" - - attribute { - name = "TestTableHashKey" - type = "S" - } - point_in_time_recovery { - enabled = true - } +resource "aws_dynamodb_table_replica" "test" { + global_table_arn = aws_dynamodb_table.test.arn + table_class_override = %[2]q - server_side_encryption { - enabled = true - kms_key_arn = aws_kms_key.test.arn + tags = { + Name = %[1]q } } - -resource "aws_kms_key" "test" { - description = %[1]q -} - -resource "aws_dynamodb_table" "test" { - name = "%[1]s-target" - restore_source_name = aws_dynamodb_table.source.name - restore_to_latest_time = true -} -`, rName) +`, rName, class)) } From f7f2ad9e382e972a894fc51316962dca27c94e64 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 12:20:08 -0400 Subject: [PATCH 07/15] Add changelog --- .changelog/26250.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/26250.txt diff --git a/.changelog/26250.txt b/.changelog/26250.txt new file mode 100644 index 00000000000..05c5639012e --- /dev/null +++ b/.changelog/26250.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_dynamodb_table_replica +``` \ No newline at end of file From 83b75e8503360743a33c502581b38e815a379e95 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 12:20:31 -0400 Subject: [PATCH 08/15] dynamodb_table_replica documentation --- internal/service/dynamodb/table_replica.go | 7 +- website/docs/r/dynamodb_table.html.markdown | 2 +- .../r/dynamodb_table_replica.html.markdown | 87 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 website/docs/r/dynamodb_table_replica.html.markdown diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go index 258b6457340..f666492231c 100644 --- a/internal/service/dynamodb/table_replica.go +++ b/internal/service/dynamodb/table_replica.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "strings" + "time" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go/aws" @@ -35,9 +36,9 @@ func ResourceTableReplica() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(createTableTimeout), - Delete: schema.DefaultTimeout(deleteTableTimeout), - Update: schema.DefaultTimeout(updateTableTimeoutTotal), + Create: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), }, CustomizeDiff: customdiff.All( diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 4ed774f1413..4ef2c36e506 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -8,7 +8,7 @@ description: |- # Resource: aws_dynamodb_table -Provides a DynamoDB table resource +Provides a DynamoDB table resource. ~> **Note:** We recommend using `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `read_capacity` and/or `write_capacity` if there's [autoscaling policy](/docs/providers/aws/r/appautoscaling_policy.html) attached to the table. diff --git a/website/docs/r/dynamodb_table_replica.html.markdown b/website/docs/r/dynamodb_table_replica.html.markdown new file mode 100644 index 00000000000..c5960715e08 --- /dev/null +++ b/website/docs/r/dynamodb_table_replica.html.markdown @@ -0,0 +1,87 @@ +--- +subcategory: "DynamoDB" +layout: "aws" +page_title: "AWS: aws_dynamodb_table_replica" +description: |- + Provides a DynamoDB table replica resource +--- + +# Resource: aws_dynamodb_table_replica + +Provides a DynamoDB table replica resource for [DynamoDB Global Tables V2 (version 2019.11.21)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V2.html). + +~> **Note:** Use `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `replica` in the associated [aws_dynamodb_table](/docs/providers/aws/r/dynamodb_table.html) configuration. + +~> **Note:** Do not use the `replica` configuration block of [aws_dynamodb_table](/docs/providers/aws/r/dynamodb_table.html) together with this resource as the two configuration options are mutually exclusive. + +## Example Usage + +### Basic Example + +```terraform +resource "aws_dynamodb_table" "example" { + provider = "awsalternate" + name = "TestTable" + hash_key = "BrodoBaggins" + billing_mode = "PAY_PER_REQUEST" + stream_enabled = true + stream_view_type = "NEW_AND_OLD_IMAGES" + + attribute { + name = "BrodoBaggins" + type = "S" + } + + lifecycle { + ignore_changes = [replica] + } +} + +resource "aws_dynamodb_table_replica" "example" { + global_table_arn = aws_dynamodb_table.example.arn + + tags = { + Name = %[1]q + Pozo = "Amargo" + } +} +``` + +## Argument Reference + +Required arguments: + +* `global_table_arn` - (Required) ARN of the _main_ or global table which this resource will replicate. + +Optional arguments: + +* `kms_key_arn` - (Optional) ARN of the CMK that should be used for the AWS KMS encryption. +* `point_in_time_recovery` - (Optional) Whether to enable Point In Time Recovery for the replica. Default is `false`. +* `table_class_override` - (Optional, Forces new resource) Storage class of the table replica. Valid values are `STANDARD` and `STANDARD_INFREQUENT_ACCESS`. If not used, the table replica will use the same class as the global table. +* `tags` - (Optional) Map of tags to populate on the created table. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the table replica. +* `id` - Name of the table and region of the main global table joined with a semicolon (_e.g._, `TableName:us-east-1`). +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `20m`) + +## Import + +DynamoDB table replicas can be imported using the `table-name:main-region`, _e.g._, + +~> **Note:** When importing, use the region where the initial or _main_ global table resides, _not_ the region of the replica. + +``` +$ terraform import aws_dynamodb_table_replica.example TestTable:us-west-2 +``` From b136f7d313621e12a1696107c87f39a5f3e1c471 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 13:25:35 -0400 Subject: [PATCH 09/15] dynamodb_table: Remove experimental code --- internal/service/dynamodb/table.go | 63 +------------------ internal/service/dynamodb/table_replica.go | 8 +-- internal/service/dynamodb/table_test.go | 68 --------------------- internal/service/dynamodb/wait.go | 20 ++---- website/docs/r/dynamodb_table.html.markdown | 4 ++ 5 files changed, 17 insertions(+), 146 deletions(-) diff --git a/internal/service/dynamodb/table.go b/internal/service/dynamodb/table.go index 70f579ddb09..0e89ad28521 100644 --- a/internal/service/dynamodb/table.go +++ b/internal/service/dynamodb/table.go @@ -93,7 +93,6 @@ func ResourceTable() *schema.Resource { // https://github.com/hashicorp/terraform-provider-aws/issues/25214 return old.(string) != new.(string) && new.(string) != "" }), - setReplicaTagsDiff, verify.SetTagsDiff, ), @@ -274,8 +273,6 @@ func ResourceTable() *schema.Resource { Type: schema.TypeString, Required: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, }, }, @@ -989,53 +986,6 @@ func resourceTableDelete(d *schema.ResourceData, meta interface{}) error { // custom diff -func setReplicaTagsDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - - resourceTags := tftags.New(diff.Get("tags").(map[string]interface{})) - - allTags := defaultTagsConfig.MergeTags(resourceTags).IgnoreConfig(ignoreTagsConfig) - - replicas := diff.Get("replica").(*schema.Set).List() - - setNew := false - tagsAllExist := false - - for i, replicaRaw := range replicas { - replica := replicaRaw.(map[string]interface{}) - - replicaAllTags := tftags.New(replica["tags"].(map[string]interface{})) - - if replica["propagate_tags"].(bool) { - replicaAllTags.Merge(allTags) - } - - if len(replica["tags_all"].(map[string]interface{})) > 0 { - tagsAllExist = true - } - - if len(replicaAllTags) > 0 { - replica["tags_all"] = replicaAllTags.Map() - setNew = true - } - - replicas[i] = replica - } - - if setNew { - if err := diff.SetNew("replica", replicas); err != nil { - return fmt.Errorf("setting new replica tags_all diff: %w", err) - } - } else if tagsAllExist { - if err := diff.SetNewComputed("replica"); err != nil { - return fmt.Errorf("setting replica tags_all to computed: %w", err) - } - } - - return nil -} - func isTableOptionDisabled(v interface{}) bool { options := v.([]interface{}) if len(options) == 0 { @@ -1125,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) } @@ -1152,14 +1102,7 @@ func updateReplicaTags(conn *dynamodb.DynamoDB, rn string, replicas []interface{ continue } - replicaTags := tftags.New(tfMap["tags"].(map[string]interface{})) - allTags := replicaTags - if v, ok := tfMap["propagate_tags"].(bool); ok && v { - allTags = replicaTags.Merge(tftags.New(newTags)) - } - - if len(allTags) > 0 { if aws.StringValue(conn.Config.Region) != region { session, err := conns.NewSessionForRegion(&conn.Config, region, terraformVersion) if err != nil { @@ -1174,7 +1117,7 @@ func updateReplicaTags(conn *dynamodb.DynamoDB, rn string, replicas []interface{ return fmt.Errorf("per region ARN for replica (%s): %w", region, err) } - if err := UpdateTags(conn, newARN, oldTags, allTags); err != nil { + if err := UpdateTags(conn, newARN, oldTags, newTags); err != nil { return fmt.Errorf("updating tags: %w", err) } } @@ -1491,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) } diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go index f666492231c..b56e6c2b575 100644 --- a/internal/service/dynamodb/table_replica.go +++ b/internal/service/dynamodb/table_replica.go @@ -154,7 +154,7 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("creating replica (%s): %w", d.Get("global_table_arn").(string), err) } - if _, err := waitReplicaActive(conn, tableName, meta.(*conns.AWSClient).Region, d.Timeout(schema.TimeoutCreate)); err != nil { + if err := waitReplicaActive(conn, tableName, meta.(*conns.AWSClient).Region, d.Timeout(schema.TimeoutCreate)); err != nil { return fmt.Errorf("waiting for replica (%s) creation: %w", meta.(*conns.AWSClient).Region, err) } @@ -397,7 +397,7 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("updating replica (%s): %w", d.Id(), err) } - if _, err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { + if err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) } } @@ -419,7 +419,7 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error } } - if _, err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { + if err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) } } @@ -483,7 +483,7 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("deleting replica (%s): %w", replicaRegion, err) } - if _, err := waitReplicaDeleted(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutDelete)); err != nil { + if err := waitReplicaDeleted(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutDelete)); err != nil { return fmt.Errorf("waiting for replica (%s) deletion: %w", replicaRegion, err) } diff --git a/internal/service/dynamodb/table_test.go b/internal/service/dynamodb/table_test.go index 6526ab88e5a..68d7497aabc 100644 --- a/internal/service/dynamodb/table_test.go +++ b/internal/service/dynamodb/table_test.go @@ -1508,37 +1508,6 @@ func TestAccDynamoDBTable_Replica_single(t *testing.T) { }) } -func TestAccDynamoDBTable_Replica_tagMagic(t *testing.T) { - var conf dynamodb.DescribeTableOutput - resourceName := "aws_dynamodb_table.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(t) - acctest.PreCheckMultipleRegion(t, 2) - }, - ErrorCheck: acctest.ErrorCheck(t, dynamodb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(t, 3), // 3 due to shared test configuration - CheckDestroy: testAccCheckTableDestroy, - Steps: []resource.TestStep{ - { - Config: testAccTableConfig_replicaMoreTags(rName), - Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckInitialTableExists(resourceName, &conf), - resource.TestCheckResourceAttr(resourceName, "replica.#", "1"), - ), - }, - { - Config: testAccTableConfig_replicaMoreTags(rName), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func TestAccDynamoDBTable_Replica_singleWithCMK(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") @@ -2841,43 +2810,6 @@ resource "aws_dynamodb_table" "test" { `, rName)) } -func testAccTableConfig_replicaMoreTags(rName string) string { - return acctest.ConfigCompose( - acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors - fmt.Sprintf(` -data "aws_region" "alternate" { - provider = "awsalternate" -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - hash_key = "TestTableHashKey" - billing_mode = "PAY_PER_REQUEST" - stream_enabled = true - stream_view_type = "NEW_AND_OLD_IMAGES" - - attribute { - name = "TestTableHashKey" - type = "S" - } - - replica { - region_name = data.aws_region.alternate.name - propagate_tags = true - - tags = { - ReplicaTag = "Bob" - } - } - - tags = { - Name = %[1]q - Pozo = "Amargo" - } -} -`, rName)) -} - func testAccTableConfig_replicaCMK(rName string) string { return acctest.ConfigCompose( acctest.ConfigMultipleRegionProvider(3), // Prevent "Provider configuration not present" errors diff --git a/internal/service/dynamodb/wait.go b/internal/service/dynamodb/wait.go index 55604fa8227..33c470702ee 100644 --- a/internal/service/dynamodb/wait.go +++ b/internal/service/dynamodb/wait.go @@ -97,7 +97,7 @@ func waitTableDeleted(conn *dynamodb.DynamoDB, tableName string, timeout time.Du return nil, err } -func waitReplicaActive(conn *dynamodb.DynamoDB, tableName, region string, timeout time.Duration) (*dynamodb.DescribeTableOutput, error) { +func waitReplicaActive(conn *dynamodb.DynamoDB, tableName, region string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{ dynamodb.ReplicaStatusCreating, @@ -111,16 +111,12 @@ func waitReplicaActive(conn *dynamodb.DynamoDB, tableName, region string, timeou Refresh: statusReplicaUpdate(conn, tableName, region), } - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*dynamodb.DescribeTableOutput); ok { - return output, err - } + _, err := stateConf.WaitForState() - return nil, err + return err } -func waitReplicaDeleted(conn *dynamodb.DynamoDB, tableName, region string, timeout time.Duration) (*dynamodb.DescribeTableOutput, error) { +func waitReplicaDeleted(conn *dynamodb.DynamoDB, tableName, region string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{ dynamodb.ReplicaStatusCreating, @@ -133,13 +129,9 @@ func waitReplicaDeleted(conn *dynamodb.DynamoDB, tableName, region string, timeo Refresh: statusReplicaDelete(conn, tableName, region), } - outputRaw, err := stateConf.WaitForState() - - if output, ok := outputRaw.(*dynamodb.DescribeTableOutput); ok { - return output, err - } + _, err := stateConf.WaitForState() - return nil, err + return err } func waitGSIActive(conn *dynamodb.DynamoDB, tableName, indexName string, timeout time.Duration) (*dynamodb.GlobalSecondaryIndexDescription, error) { //nolint:unparam diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 4ef2c36e506..4f68da7c51a 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -12,6 +12,8 @@ Provides a DynamoDB table resource. ~> **Note:** We recommend using `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `read_capacity` and/or `write_capacity` if there's [autoscaling policy](/docs/providers/aws/r/appautoscaling_policy.html) attached to the table. +~> **Note:** When using [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) with this resource, use `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `replica`. + ## DynamoDB Table attributes Only define attributes on the table object that are going to be used as: @@ -77,6 +79,8 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" { This resource implements support for [DynamoDB Global Tables V2 (version 2019.11.21)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V2.html) via `replica` configuration blocks. For working with [DynamoDB Global Tables V1 (version 2017.11.29)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V1.html), see the [`aws_dynamodb_global_table` resource](/docs/providers/aws/r/dynamodb_global_table.html). +~> **Note:** [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) is an alternate way of configuring Global Tables. Do not use `replica` configuration blocks of `aws_dynamodb_table` together with with [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html). + ```terraform resource "aws_dynamodb_table" "example" { name = "example" From 6e89221e8f36b35d204d9274a637adc48ec31308 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 13:33:40 -0400 Subject: [PATCH 10/15] docs: Lint --- website/docs/r/dynamodb_table.html.markdown | 2 +- website/docs/r/dynamodb_table_replica.html.markdown | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 4f68da7c51a..12684075d25 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -12,7 +12,7 @@ Provides a DynamoDB table resource. ~> **Note:** We recommend using `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `read_capacity` and/or `write_capacity` if there's [autoscaling policy](/docs/providers/aws/r/appautoscaling_policy.html) attached to the table. -~> **Note:** When using [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) with this resource, use `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `replica`. +~> **Note:** When using [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) with this resource, use `lifecycle` [`ignore_changes`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) for `replica`, _e.g._, `lifecycle { ignore_changes = [replica] }`. ## DynamoDB Table attributes diff --git a/website/docs/r/dynamodb_table_replica.html.markdown b/website/docs/r/dynamodb_table_replica.html.markdown index c5960715e08..372521fb9e1 100644 --- a/website/docs/r/dynamodb_table_replica.html.markdown +++ b/website/docs/r/dynamodb_table_replica.html.markdown @@ -19,8 +19,18 @@ Provides a DynamoDB table replica resource for [DynamoDB Global Tables V2 (versi ### Basic Example ```terraform +provider "aws" { + alias = "main" + region = "us-west-2" +} + +provider "aws" { + alias = "alt" + region = "us-east-2" +} + resource "aws_dynamodb_table" "example" { - provider = "awsalternate" + provider = "aws.main" name = "TestTable" hash_key = "BrodoBaggins" billing_mode = "PAY_PER_REQUEST" @@ -38,6 +48,7 @@ resource "aws_dynamodb_table" "example" { } resource "aws_dynamodb_table_replica" "example" { + provider = "aws.alt" global_table_arn = aws_dynamodb_table.example.arn tags = { From 72e25fcb04f4acbe03d590119f128eef613ba42e Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 13:34:58 -0400 Subject: [PATCH 11/15] docs: Lint --- website/docs/r/dynamodb_table_replica.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/dynamodb_table_replica.html.markdown b/website/docs/r/dynamodb_table_replica.html.markdown index 372521fb9e1..c71cf468280 100644 --- a/website/docs/r/dynamodb_table_replica.html.markdown +++ b/website/docs/r/dynamodb_table_replica.html.markdown @@ -52,7 +52,7 @@ resource "aws_dynamodb_table_replica" "example" { global_table_arn = aws_dynamodb_table.example.arn tags = { - Name = %[1]q + Name = "IZPAWS" Pozo = "Amargo" } } From 346c66a997efeae29ab1ce27cda34c8384cf720a Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 14:08:24 -0400 Subject: [PATCH 12/15] dynamodb_table_replica: Clean up errors --- internal/service/dynamodb/table_replica.go | 74 ++++++++++--------- .../service/dynamodb/table_replica_test.go | 20 +++++ website/docs/r/dynamodb_table.html.markdown | 2 +- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/internal/service/dynamodb/table_replica.go b/internal/service/dynamodb/table_replica.go index b56e6c2b575..7eadb324e35 100644 --- a/internal/service/dynamodb/table_replica.go +++ b/internal/service/dynamodb/table_replica.go @@ -23,6 +23,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) +const ( + ResNameTableReplica = "Table Replica" +) + func ResourceTableReplica() *schema.Resource { //lintignore:R011 return &schema.Resource{ @@ -88,16 +92,16 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error mainRegion, err := RegionFromARN(d.Get("global_table_arn").(string)) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Get("global_table_arn").(string), err) } if mainRegion == aws.StringValue(conn.Config.Region) { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as main table")) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Get("global_table_arn").(string), errors.New("replica cannot be in same region as main table")) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Get("global_table_arn").(string), fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now main table region @@ -116,7 +120,7 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error tableName, err := TableNameFromARN(d.Get("global_table_arn").(string)) if err != nil { - return fmt.Errorf("creating replica of %s: %w", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Get("global_table_arn").(string), err) } input := &dynamodb.UpdateTableInput{ @@ -151,18 +155,18 @@ func resourceTableReplicaCreate(d *schema.ResourceData, meta interface{}) error } if err != nil { - return fmt.Errorf("creating replica (%s): %w", d.Get("global_table_arn").(string), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Get("global_table_arn").(string), err) } if err := waitReplicaActive(conn, tableName, meta.(*conns.AWSClient).Region, d.Timeout(schema.TimeoutCreate)); err != nil { - return fmt.Errorf("waiting for replica (%s) creation: %w", meta.(*conns.AWSClient).Region, err) + return create.Error(names.DynamoDB, create.ErrActionWaitingForCreation, ResNameTableReplica, d.Get("global_table_arn").(string), err) } d.SetId(tableReplicaID(tableName, mainRegion)) repARN, err := ARNForNewRegion(d.Get("global_table_arn").(string), replicaRegion) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCreating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionCreating, ResNameTableReplica, d.Id(), err) } d.Set("arn", repARN) @@ -182,7 +186,7 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), err) } globalTableARN := arn.ARN{ @@ -196,12 +200,12 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { d.Set("global_table_arn", globalTableARN) if mainRegion == replicaRegion { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("replica cannot be in same region as main table")) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), errors.New("replica cannot be in same region as main table")) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now main table region @@ -217,27 +221,27 @@ func resourceTableReplicaRead(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), err) } if result == nil || result.Table == nil { if d.IsNewResource() { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), errors.New("empty output after creation")) } - create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id()) d.SetId("") return nil } replica, err := FilterReplicasByRegion(result.Table.Replicas, replicaRegion) if !d.IsNewResource() && err != nil && err.Error() == "no replicas found" { - create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id()) d.SetId("") return nil } if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), err) } d.Set("kms_key_arn", replica.KMSMasterKeyId) @@ -260,7 +264,7 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e tableName, _, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), err) } result, err := conn.DescribeTable(&dynamodb.DescribeTableInput{ @@ -274,14 +278,14 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e } if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), err) } if result == nil || result.Table == nil { if d.IsNewResource() { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), errors.New("empty output after creation")) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), errors.New("empty output after creation")) } - create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id()) + create.LogNotFoundRemoveState(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id()) d.SetId("") return nil } @@ -293,7 +297,7 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e }) if err != nil && !tfawserr.ErrCodeEquals(err, "UnknownOperationException") { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("continuous backups: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), fmt.Errorf("continuous backups: %w", err)) } if pitrOut != nil && pitrOut.ContinuousBackupsDescription != nil && pitrOut.ContinuousBackupsDescription.PointInTimeRecoveryDescription != nil { @@ -308,18 +312,18 @@ func resourceTableReplicaReadReplica(d *schema.ResourceData, meta interface{}) e tags, err := ListTags(conn, d.Get("arn").(string)) if err != nil && !tfawserr.ErrMessageContains(err, "UnknownOperationException", "Tagging is not currently supported in DynamoDB Local.") { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), fmt.Errorf("tags: %w", err)) + return create.Error(names.DynamoDB, create.ErrActionReading, ResNameTableReplica, d.Id(), fmt.Errorf("tags: %w", err)) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags", err) + return create.SettingError(names.DynamoDB, ResNameTableReplica, d.Id(), "tags", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return create.SettingError(names.DynamoDB, "Table Replica", d.Id(), "tags_all", err) + return create.SettingError(names.DynamoDB, ResNameTableReplica, d.Id(), "tags_all", err) } return nil @@ -335,18 +339,18 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), err) } replicaRegion := aws.StringValue(repConn.Config.Region) if mainRegion == replicaRegion { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), errors.New("replica cannot be in same region as main table")) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), errors.New("replica cannot be in same region as main table")) } session, err := conns.NewSessionForRegion(&repConn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), fmt.Errorf("region %s: %w", mainRegion, err)) } tabConn := dynamodb.New(session) // now main table region @@ -394,11 +398,11 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error } if err != nil && !tfawserr.ErrMessageContains(err, "ValidationException", "no actions specified") { - return fmt.Errorf("updating replica (%s): %w", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), err) } if err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionWaitingForUpdate, ResNameTableReplica, d.Id(), err) } } @@ -409,18 +413,18 @@ func resourceTableReplicaUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") if err := UpdateTags(repConn, d.Get("arn").(string), o, n); err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), err) } } if d.HasChange("point_in_time_recovery") { if err := updatePITR(repConn, tableName, d.Get("point_in_time_recovery").(bool), replicaRegion, meta.(*conns.AWSClient).TerraformVersion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.Error(names.DynamoDB, create.ErrActionUpdating, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionUpdating, ResNameTableReplica, d.Id(), err) } } if err := waitReplicaActive(tabConn, tableName, replicaRegion, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("waiting for replica (%s) update: %w", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionWaitingForUpdate, ResNameTableReplica, d.Id(), err) } } @@ -432,7 +436,7 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error tableName, mainRegion, err := TableReplicaParseID(d.Id()) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionDeleting, "Table Replica", d.Id(), err) + return create.Error(names.DynamoDB, create.ErrActionDeleting, ResNameTableReplica, d.Id(), err) } conn := meta.(*conns.AWSClient).DynamoDBConn @@ -441,7 +445,7 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, meta.(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionDeleting, ResNameTableReplica, d.Id(), fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now main table region @@ -480,11 +484,11 @@ func resourceTableReplicaDelete(d *schema.ResourceData, meta interface{}) error } if err != nil { - return fmt.Errorf("deleting replica (%s): %w", replicaRegion, err) + return create.Error(names.DynamoDB, create.ErrActionDeleting, ResNameTableReplica, d.Id(), err) } if err := waitReplicaDeleted(conn, tableName, replicaRegion, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("waiting for replica (%s) deletion: %w", replicaRegion, err) + return create.Error(names.DynamoDB, create.ErrActionWaitingForDeletion, ResNameTableReplica, d.Id(), err) } return nil diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index a7f00a65b1d..013f513eb8e 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -20,6 +20,10 @@ import ( ) func TestAccDynamoDBTableReplica_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -47,6 +51,10 @@ func TestAccDynamoDBTableReplica_basic(t *testing.T) { } func TestAccDynamoDBTableReplica_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var table1 dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -70,6 +78,10 @@ func TestAccDynamoDBTableReplica_disappears(t *testing.T) { } func TestAccDynamoDBTableReplica_pitr(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -97,6 +109,10 @@ func TestAccDynamoDBTableReplica_pitr(t *testing.T) { } func TestAccDynamoDBTableReplica_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -155,6 +171,10 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { } func TestAccDynamoDBTableReplica_tableClass(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 12684075d25..ee7697a329d 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -79,7 +79,7 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" { This resource implements support for [DynamoDB Global Tables V2 (version 2019.11.21)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V2.html) via `replica` configuration blocks. For working with [DynamoDB Global Tables V1 (version 2017.11.29)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V1.html), see the [`aws_dynamodb_global_table` resource](/docs/providers/aws/r/dynamodb_global_table.html). -~> **Note:** [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) is an alternate way of configuring Global Tables. Do not use `replica` configuration blocks of `aws_dynamodb_table` together with with [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html). +~> **Note:** [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html) is an alternate way of configuring Global Tables. Do not use `replica` configuration blocks of `aws_dynamodb_table` together with [aws_dynamodb_table_replica](/docs/providers/aws/r/dynamodb_table_replica.html). ```terraform resource "aws_dynamodb_table" "example" { From adbfbe73179507a5478a9c2b91599e55bcc39723 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 14:15:04 -0400 Subject: [PATCH 13/15] tests/dynamodb_table_replica: Clean up errors --- .../service/dynamodb/table_replica_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index 013f513eb8e..95e60a4b39a 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -220,17 +220,17 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { log.Printf("[DEBUG] Checking if DynamoDB table replica %s was destroyed", rs.Primary.ID) if rs.Primary.ID == "" { - return fmt.Errorf("table replica has no ID!") + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, errors.New("no ID")) } tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, err) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now global table region @@ -246,7 +246,7 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { } if err != nil { - return fmt.Errorf("checking table replica existence %s still exists: %s", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, err) } if result == nil || result.Table == nil { @@ -268,23 +268,23 @@ func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutpu log.Printf("[DEBUG] Trying to create initial table replica state!") rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", n) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, fmt.Errorf("not found: %s", n)) } if rs.Primary.ID == "" { - return fmt.Errorf("table replica has no ID!") + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, errors.New("no ID")) } conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionReading, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, err) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) if err != nil { - return fmt.Errorf("new session for region (%s): %w", mainRegion, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now global table region @@ -295,7 +295,7 @@ func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutpu _, err = conn.DescribeTable(params) if err != nil { - return fmt.Errorf("describing table replica (%s): %s", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, err) } return nil From 443dbdbe3ef9d94c645b489315c59a185d53b363 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 14:15:52 -0400 Subject: [PATCH 14/15] tests/dynamodb_table_replica: Clean up errors --- .../service/dynamodb/table_replica_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index 95e60a4b39a..04238b874d3 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -220,17 +220,17 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { log.Printf("[DEBUG] Checking if DynamoDB table replica %s was destroyed", rs.Primary.ID) if rs.Primary.ID == "" { - return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, errors.New("no ID")) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, tfdynamodb.ResNameTableReplica, rs.Primary.ID, errors.New("no ID")) } tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, tfdynamodb.ResNameTableReplica, rs.Primary.ID, err) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, tfdynamodb.ResNameTableReplica, rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now global table region @@ -246,7 +246,7 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { } if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, tfdynamodb.ResNameTableReplica, rs.Primary.ID, err) } if result == nil || result.Table == nil { @@ -254,7 +254,7 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { } if _, err := tfdynamodb.FilterReplicasByRegion(result.Table.Replicas, replicaRegion); err == nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, "Table Replica", rs.Primary.ID, errors.New("still exists")) + return create.Error(names.DynamoDB, create.ErrActionCheckingDestroyed, tfdynamodb.ResNameTableReplica, rs.Primary.ID, errors.New("still exists")) } return err @@ -268,23 +268,23 @@ func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutpu log.Printf("[DEBUG] Trying to create initial table replica state!") rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, fmt.Errorf("not found: %s", n)) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, tfdynamodb.ResNameTableReplica, rs.Primary.ID, fmt.Errorf("not found: %s", n)) } if rs.Primary.ID == "" { - return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, errors.New("no ID")) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, tfdynamodb.ResNameTableReplica, rs.Primary.ID, errors.New("no ID")) } conn := acctest.Provider.Meta().(*conns.AWSClient).DynamoDBConn tableName, mainRegion, err := tfdynamodb.TableReplicaParseID(rs.Primary.ID) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, tfdynamodb.ResNameTableReplica, rs.Primary.ID, err) } session, err := conns.NewSessionForRegion(&conn.Config, mainRegion, acctest.Provider.Meta().(*conns.AWSClient).TerraformVersion) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, tfdynamodb.ResNameTableReplica, rs.Primary.ID, fmt.Errorf("region %s: %w", mainRegion, err)) } conn = dynamodb.New(session) // now global table region @@ -295,7 +295,7 @@ func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutpu _, err = conn.DescribeTable(params) if err != nil { - return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, "Table Replica", rs.Primary.ID, err) + return create.Error(names.DynamoDB, create.ErrActionCheckingExistence, tfdynamodb.ResNameTableReplica, rs.Primary.ID, err) } return nil From 58a8065dfd125f0bbe7df50d172c8331fdd82eb0 Mon Sep 17 00:00:00 2001 From: Dirk Avery Date: Thu, 11 Aug 2022 14:19:53 -0400 Subject: [PATCH 15/15] Remove unused --- .../service/dynamodb/table_replica_test.go | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/internal/service/dynamodb/table_replica_test.go b/internal/service/dynamodb/table_replica_test.go index 04238b874d3..6dbcc7f61a5 100644 --- a/internal/service/dynamodb/table_replica_test.go +++ b/internal/service/dynamodb/table_replica_test.go @@ -24,7 +24,6 @@ func TestAccDynamoDBTableReplica_basic(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -37,7 +36,7 @@ func TestAccDynamoDBTableReplica_basic(t *testing.T) { { Config: testAccTableReplicaConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), ), }, @@ -55,7 +54,6 @@ func TestAccDynamoDBTableReplica_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var table1 dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -68,7 +66,7 @@ func TestAccDynamoDBTableReplica_disappears(t *testing.T) { { Config: testAccTableReplicaConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &table1), + testAccCheckTableReplicaExists(resourceName), acctest.CheckResourceDisappears(acctest.Provider, tfdynamodb.ResourceTableReplica(), resourceName), ), ExpectNonEmptyPlan: true, @@ -82,7 +80,6 @@ func TestAccDynamoDBTableReplica_pitr(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -95,7 +92,7 @@ func TestAccDynamoDBTableReplica_pitr(t *testing.T) { { Config: testAccTableReplicaConfig_pitr(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "point_in_time_recovery", "true"), ), }, @@ -113,7 +110,6 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -126,7 +122,7 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { { Config: testAccTableReplicaConfig_tags1(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.tape", "Valladolid"), ), @@ -139,7 +135,7 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { { Config: testAccTableReplicaConfig_tags2(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "5"), resource.TestCheckResourceAttr(resourceName, "tags.arise", "Melandru"), resource.TestCheckResourceAttr(resourceName, "tags.brightest", "Lights"), @@ -156,7 +152,7 @@ func TestAccDynamoDBTableReplica_tags(t *testing.T) { { Config: testAccTableReplicaConfig_tags3(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), ), @@ -175,7 +171,6 @@ func TestAccDynamoDBTableReplica_tableClass(t *testing.T) { t.Skip("skipping long-running test in short mode") } - var conf dynamodb.DescribeTableOutput resourceName := "aws_dynamodb_table_replica.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -188,7 +183,7 @@ func TestAccDynamoDBTableReplica_tableClass(t *testing.T) { { Config: testAccTableReplicaConfig_tableClass(rName, "STANDARD"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "table_class_override", "STANDARD"), ), }, @@ -200,7 +195,7 @@ func TestAccDynamoDBTableReplica_tableClass(t *testing.T) { { Config: testAccTableReplicaConfig_tableClass(rName, "STANDARD_INFREQUENT_ACCESS"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckTableReplicaExists(resourceName, &conf), + testAccCheckTableReplicaExists(resourceName), resource.TestCheckResourceAttr(resourceName, "table_class_override", "STANDARD_INFREQUENT_ACCESS"), ), }, @@ -263,7 +258,7 @@ func testAccCheckTableReplicaDestroy(s *terraform.State) error { return nil } -func testAccCheckTableReplicaExists(n string, table *dynamodb.DescribeTableOutput) resource.TestCheckFunc { +func testAccCheckTableReplicaExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { log.Printf("[DEBUG] Trying to create initial table replica state!") rs, ok := s.RootModule().Resources[n]