Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

dynamodb/table: Add replica propagate_tags #25866

Merged
merged 8 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/25866.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_dynamodb_table: Add `replica.*.propagate_tags` argument to allow propagating tags to replicas
```
16 changes: 16 additions & 0 deletions internal/service/dynamodb/arn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dynamodb

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

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

parsedARN.Region = newRegion

return parsedARN.String(), nil
}
50 changes: 50 additions & 0 deletions internal/service/dynamodb/arn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dynamodb_test

import (
"testing"

tfdynamodb "github.com/hashicorp/terraform-provider-aws/internal/service/dynamodb"
)

func TestARNForNewRegion(t *testing.T) {
testCases := []struct {
TestName string
ARN string
NewRegion string
ExpectedARN string
ErrorExpected bool
}{
{
TestName: "basic",
ARN: "arn:aws:dynamodb:us-west-2:786648903940:table/tf-acc-test-7864711876941043153",
NewRegion: "us-east-2",
ExpectedARN: "arn:aws:dynamodb:us-east-2:786648903940:table/tf-acc-test-7864711876941043153",
ErrorExpected: false,
},
{
TestName: "basic2",
ARN: "arn:aws:dynamodb:us-west-2:786648903940:table/tf-acc-test-7864711876941043153",
NewRegion: "us-east-1",
ExpectedARN: "arn:aws:dynamodb:us-east-1:786648903940:table/tf-acc-test-7864711876941043153",
ErrorExpected: false,
},
}

for _, testCase := range testCases {
t.Run(testCase.TestName, func(t *testing.T) {
got, err := tfdynamodb.ARNForNewRegion(testCase.ARN, testCase.NewRegion)

if err != nil && !testCase.ErrorExpected {
t.Errorf("did not expect an error but got one: %s", err)
}

if err == nil && testCase.ErrorExpected {
t.Error("expected an error but got none")
}

if got != testCase.ExpectedARN {
t.Errorf("got %s, expected %s", got, testCase.ExpectedARN)
}
})
}
}
208 changes: 147 additions & 61 deletions internal/service/dynamodb/table.go

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions internal/service/dynamodb/table_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynamodb_test

import (
"errors"
"fmt"
"log"
"regexp"
Expand All @@ -16,6 +17,7 @@ import (
"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 init() {
Expand Down Expand Up @@ -1601,6 +1603,86 @@ func TestAccDynamoDBTable_Replica_pitr(t *testing.T) {
})
}

func TestAccDynamoDBTable_Replica_tagsOneOfTwo(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var conf dynamodb.DescribeTableOutput
var providers []*schema.Provider
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),
ProviderFactories: acctest.FactoriesMultipleRegion(&providers, 3),
CheckDestroy: testAccCheckTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccTableConfig_replicaTags(rName, "benny", "smiles", true, false),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckInitialTableExists(resourceName, &conf),
testAccCheckReplicaHasTags(resourceName, acctest.AlternateRegion(), true),
testAccCheckReplicaHasTags(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 TestAccDynamoDBTable_Replica_tagsTwoOfTwo(t *testing.T) {
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var conf dynamodb.DescribeTableOutput
var providers []*schema.Provider
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),
ProviderFactories: acctest.FactoriesMultipleRegion(&providers, 3),
CheckDestroy: testAccCheckTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccTableConfig_replicaTags(rName, "Structure", "Adobe", true, true),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckInitialTableExists(resourceName, &conf),
testAccCheckReplicaHasTags(resourceName, acctest.AlternateRegion(), true),
testAccCheckReplicaHasTags(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 TestAccDynamoDBTable_tableClassInfrequentAccess(t *testing.T) {
var table dynamodb.DescribeTableOutput
resourceName := "aws_dynamodb_table.test"
Expand Down Expand Up @@ -1777,6 +1859,53 @@ func testAccCheckInitialTableExists(n string, table *dynamodb.DescribeTableOutpu
}
}

func testAccCheckReplicaHasTags(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 testAccCheckInitialTableConf(resourceName string) resource.TestCheckFunc {
return resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "hash_key", "TestTableHashKey"),
Expand Down Expand Up @@ -2810,6 +2939,49 @@ resource "aws_dynamodb_table" "test" {
`, rName))
}

func testAccTableConfig_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 testAccTableConfig_lsi(rName, lsiName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "test" {
Expand Down
2 changes: 1 addition & 1 deletion internal/service/dynamodb/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func waitKinesisStreamingDestinationDisabled(ctx context.Context, conn *dynamodb
return err
}

func waitTableActive(conn *dynamodb.DynamoDB, tableName string, timeout time.Duration) (*dynamodb.TableDescription, error) { //nolint:unparam
func waitTableActive(conn *dynamodb.DynamoDB, tableName string, timeout time.Duration) (*dynamodb.TableDescription, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{
dynamodb.TableStatusCreating,
Expand Down
7 changes: 7 additions & 0 deletions names/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
ErrActionCheckingExistence = "checking existence"
ErrActionCheckingNotRecreated = "checking not recreated"
ErrActionCheckingRecreated = "checking recreated"
ErrActionChecking = "checking"
ErrActionCreating = "creating"
ErrActionDeleting = "deleting"
ErrActionReading = "reading"
Expand Down Expand Up @@ -54,6 +55,12 @@ func DiagError(service, action, resource, id string, gotError error) diag.Diagno
}
}

// ErrorSetting returns an errors.Error with a standardized error message when setting
// arguments and attributes values.
func ErrorSetting(service, resource, id, argument string, gotError error) error {
return errors.New(ProblemStandardMessage(service, fmt.Sprintf("%s %s", ErrActionSetting, argument), resource, id, gotError))
}

// AddWarning returns diag.Diagnostics with an additional diag.Diagnostic containing
// a warning using a standardized problem message
func AddWarning(diags diag.Diagnostics, service, action, resource, id string, gotError error) diag.Diagnostics {
Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/ami.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ several valid keys, for a full reference, check out
* `name_regex` - (Optional) A regex string to apply to the AMI list returned
by AWS. This allows more advanced filtering not supported from the AWS API. This
filtering is done locally on what AWS returns, and could have a performance
impact if the result is large. It is recommended to combine this with other
impact if the result is large. Combine this with other
options to narrow down the list AWS returns.

~> **NOTE:** If more or less than a single match is returned by the search,
Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/ami_ids.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ are several valid keys, for a full reference, check out
* `name_regex` - (Optional) A regex string to apply to the AMI list returned
by AWS. This allows more advanced filtering not supported from the AWS API.
This filtering is done locally on what AWS returns, and could have a performance
impact if the result is large. It is recommended to combine this with other
impact if the result is large. Combine this with other
options to narrow down the list AWS returns.

* `sort_ascending` - (Defaults to `false`) Used to sort AMIs by creation time.
Expand Down
4 changes: 1 addition & 3 deletions website/docs/d/iam_roles.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ output "arns" {

The following arguments are supported:

* `name_regex` - (Optional) A regex string to apply to the IAM roles list returned by AWS. This allows more advanced filtering not supported from the AWS API.
This filtering is done locally on what AWS returns, and could have a performance impact if the result is large. It is recommended to combine this with other
options to narrow down the list AWS returns.
* `name_regex` - (Optional) A regex string to apply to the IAM roles list returned by AWS. This allows more advanced filtering not supported from the AWS API. This filtering is done locally on what AWS returns, and could have a performance impact if the result is large. Combine this with other options to narrow down the list AWS returns.
* `path_prefix` - (Optional) The path prefix for filtering the results. For example, the prefix `/application_abc/component_xyz/` gets all roles whose path starts with `/application_abc/component_xyz/`. If it is not included, it defaults to a slash (`/`), listing all roles. For more details, check out [list-roles in the AWS CLI reference][1].

## Attributes Reference
Expand Down
4 changes: 1 addition & 3 deletions website/docs/d/iam_users.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ data "aws_iam_users" "users" {

The following arguments are supported:

* `name_regex` - (Optional) A regex string to apply to the IAM users list returned by AWS. This allows more advanced filtering not supported from the AWS API.
This filtering is done locally on what AWS returns, and could have a performance impact if the result is large. It is recommended to combine this with other
options to narrow down the list AWS returns.
* `name_regex` - (Optional) Regex string to apply to the IAM users list returned by AWS. This allows more advanced filtering not supported from the AWS API. This filtering is done locally on what AWS returns, and could have a performance impact if the result is large. Combine this with other options to narrow down the list AWS returns.
* `path_prefix` - (Optional) The path prefix for filtering the results. For example, the prefix `/division_abc/subdivision_xyz/` gets all users whose path starts with `/division_abc/subdivision_xyz/`. If it is not included, it defaults to a slash (`/`), listing all users. For more details, check out [list-users in the AWS CLI reference][1].

## Attributes Reference
Expand Down
2 changes: 1 addition & 1 deletion website/docs/d/memorydb_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ In addition, the following attributes are exported:
* `engine_version` - Version number of the Redis engine used by the cluster.
* `final_snapshot_name` - Name of the final cluster snapshot to be created when this resource is deleted. If omitted, no final snapshot will be made.
* `kms_key_arn` - ARN of the KMS key used to encrypt the cluster at rest.
* `maintenance_window` - The weekly time range during which maintenance on the cluster is performed. It is specified as a range in the format `ddd:hh24:mi-ddd:hh24:mi` (24H Clock UTC). Example: `sun:23:00-mon:01:30`.
* `maintenance_window` - The weekly time range during which maintenance on the cluster is performed. Specify as a range in the format `ddd:hh24:mi-ddd:hh24:mi` (24H Clock UTC). Example: `sun:23:00-mon:01:30`.
* `node_type` - The compute and memory capacity of the nodes in the cluster.
* `num_replicas_per_shard` - The number of replicas to apply to each shard.
* `num_shards` - The number of shards in the cluster.
Expand Down
2 changes: 1 addition & 1 deletion website/docs/guides/version-2-upgrade.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Upgrade topics:

-> Before upgrading to version 2.0.0 or later, it is recommended to upgrade to the most recent 1.X version of the provider (version 1.60.0) and ensure that your environment successfully runs [`terraform plan`](https://www.terraform.io/docs/commands/plan.html) without unexpected changes or deprecation notices.

It is recommended to use [version constraints when configuring Terraform providers](https://www.terraform.io/docs/configuration/providers.html#provider-versions). If you are following that recommendation, update the version constraints in your Terraform configuration and run [`terraform init`](https://www.terraform.io/docs/commands/init.html) to download the new version.
We recommend using [version constraints when configuring Terraform providers](https://www.terraform.io/docs/configuration/providers.html#provider-versions). If you are following that recommendation, update the version constraints in your Terraform configuration and run [`terraform init`](https://www.terraform.io/docs/commands/init.html) to download the new version.

Update to latest 1.X version:

Expand Down
4 changes: 2 additions & 2 deletions website/docs/guides/version-3-upgrade.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Upgrade topics:

-> Before upgrading to version 3.0.0, it is recommended to upgrade to the most recent 2.X version of the provider and ensure that your environment successfully runs [`terraform plan`](https://www.terraform.io/docs/commands/plan.html) without unexpected changes or deprecation notices.

It is recommended to use [version constraints when configuring Terraform providers](https://www.terraform.io/docs/configuration/providers.html#provider-versions). If you are following that recommendation, update the version constraints in your Terraform configuration and run [`terraform init`](https://www.terraform.io/docs/commands/init.html) to download the new version.
We recommend using [version constraints when configuring Terraform providers](https://www.terraform.io/docs/configuration/providers.html#provider-versions). If you are following that recommendation, update the version constraints in your Terraform configuration and run [`terraform init`](https://www.terraform.io/docs/commands/init.html) to download the new version.

For example, given this previous configuration:

Expand Down Expand Up @@ -479,7 +479,7 @@ Terraform will perform the following actions:
Plan: 5 to add, 0 to change, 5 to destroy.
```

Due to the type of configuration change, Terraform does not know that the previous `aws_route53_record` resources (indexed by number in the existing state) and the new resources (indexed by domain names in the updated configuration) are equivalent. Typically in this situation, the [`terraform state mv` command](https://www.terraform.io/docs/commands/state/mv.html) can be used to reduce the plan to show no changes. This is done by associating the count index (e.g., `[1]`) with the equivalent domain name index (e.g., `["existing2.example.com"]`), making one of the four commands to fix the above example: `terraform state mv 'aws_route53_record.existing[1]' 'aws_route53_record.existing["existing2.example.com"]'`. It is recommended to use this `terraform state mv` update process where possible to reduce chances of unexpected behaviors or changes in an environment.
Due to the type of configuration change, Terraform does not know that the previous `aws_route53_record` resources (indexed by number in the existing state) and the new resources (indexed by domain names in the updated configuration) are equivalent. Typically in this situation, the [`terraform state mv` command](https://www.terraform.io/docs/commands/state/mv.html) can be used to reduce the plan to show no changes. This is done by associating the count index (e.g., `[1]`) with the equivalent domain name index (e.g., `["existing2.example.com"]`), making one of the four commands to fix the above example: `terraform state mv 'aws_route53_record.existing[1]' 'aws_route53_record.existing["existing2.example.com"]'`. We recommend using this `terraform state mv` update process where possible to reduce chances of unexpected behaviors or changes in an environment.

If using `terraform state mv` to reduce the plan to show no changes, no additional steps are required.

Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/ami.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ it's better to use `aws_ami_launch_permission` instead.

```terraform
# Create an AMI that will start a machine whose root device is backed by
# an EBS volume populated from a snapshot. It is assumed that such a snapshot
# an EBS volume populated from a snapshot. We assume that such a snapshot
# already exists with the id "snap-xxxxxxxx".
resource "aws_ami" "example" {
name = "terraform-example"
Expand Down
Loading