From d56b9a2edd6e2b2cd635ebc571c89d6f7e76bd22 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 27 Nov 2024 12:23:17 -0600 Subject: [PATCH 1/8] aws_rds_cluster: add ability to promote replica cluster to standalone --- internal/service/rds/cluster.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/internal/service/rds/cluster.go b/internal/service/rds/cluster.go index 096dda7d722..4ebc785f68c 100644 --- a/internal/service/rds/cluster.go +++ b/internal/service/rds/cluster.go @@ -1394,6 +1394,24 @@ func resourceClusterUpdate(ctx context.Context, d *schema.ResourceData, meta int } } + if d.HasChange("replication_source_identifier") { + if d.Get("replication_source_identifier").(string) == "" { + input := rds.PromoteReadReplicaDBClusterInput{ + DBClusterIdentifier: aws.String(d.Id()), + } + _, err := conn.PromoteReadReplicaDBCluster(ctx, &input) + if err != nil { + return sdkdiag.AppendErrorf(diags, "promoting read replica to primary for RDS Cluster (%s): %s", d.Id(), err) + } + + if _, err := waitDBClusterAvailable(ctx, conn, d.Id(), false, d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for RDS Cluster (%s) update: %s", d.Id(), err) + } + } else { + return sdkdiag.AppendErrorf(diags, "promoting to standalone is not supported for RDS Cluster (%s)", d.Id()) + } + } + if d.HasChangesExcept( names.AttrAllowMajorVersionUpgrade, "delete_automated_backups", From 04dbbbae0344a64decc782fbe556578480212edf Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 27 Nov 2024 16:16:09 -0600 Subject: [PATCH 2/8] aws_rds_cluster: add test --- internal/service/rds/cluster_test.go | 214 ++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 1 deletion(-) diff --git a/internal/service/rds/cluster_test.go b/internal/service/rds/cluster_test.go index b7d937c83f7..5a00c6d610f 100644 --- a/internal/service/rds/cluster_test.go +++ b/internal/service/rds/cluster_test.go @@ -1291,6 +1291,51 @@ func TestAccRDSCluster_copyTagsToSnapshot_restorePointInTime(t *testing.T) { }) } +func TestAccRDSCluster_ReplicationSourceIdentifier_promote(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var primaryCluster types.DBCluster + var replicaCluster types.DBCluster + resourceName := "aws_rds_cluster.test" + resourceName2 := "aws_rds_cluster.alternate" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + // record the initialized providers so that we can use them to + // check for the cluster in each region + var providers []*schema.Provider + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckMultipleRegion(t, 2) + }, + ErrorCheck: acctest.ErrorCheck(t, names.RDSServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5FactoriesPlusProvidersAlternate(ctx, t, &providers), + CheckDestroy: acctest.CheckWithProviders(testAccCheckClusterDestroyWithProvider(ctx), &providers), + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_replicationSource_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(acctest.Region(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(acctest.AlternateRegion(), &providers)), + resource.TestCheckResourceAttrSet(resourceName2, "replication_source_identifier"), + ), + }, + { + Config: testAccClusterConfig_replicationSource_promote(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(acctest.Region(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(acctest.AlternateRegion(), &providers)), + resource.TestCheckResourceAttr(resourceName2, "replication_source_identifier", ""), + ), + }, + }, + }) +} + func TestAccRDSCluster_ReplicationSourceIdentifier_kmsKeyID(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -3379,7 +3424,7 @@ data "aws_rds_orderable_db_instance" "test" { resource "aws_rds_cluster" "test" { apply_immediately = true db_subnet_group_name = aws_db_subnet_group.test.name - ca_certificate_identifier = "rds-ca-2019" + ca_certificate_identifier = "rds-ca-rsa2048-g1" cluster_identifier = %[1]q engine = data.aws_rds_orderable_db_instance.test.engine engine_version = data.aws_rds_orderable_db_instance.test.engine_version @@ -4345,6 +4390,166 @@ resource "aws_rds_cluster" "alternate" { `, tfrds.ClusterEngineAuroraMySQL, mainInstanceClasses, rName)) } +func testAccClusterConfig_replicationSource_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigMultipleRegionProvider(2), fmt.Sprintf(` +data "aws_availability_zones" "available" { + provider = "awsalternate" + + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} +data "aws_region" "current" {} + +data "aws_rds_engine_version" "default" { + engine = %[1]q +} + +data "aws_rds_orderable_db_instance" "test" { + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + preferred_instance_classes = [%[2]s] +} + +resource "aws_rds_cluster_parameter_group" "test" { + name = %[3]q + family = data.aws_rds_engine_version.default.parameter_group_family + description = "RDS default cluster parameter group" + + parameter { + name = "binlog_format" + value = "STATEMENT" + apply_method = "pending-reboot" + } +} + +resource "aws_rds_cluster" "test" { + cluster_identifier = "%[3]s-primary" + db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.test.name + database_name = "test" + engine = data.aws_rds_engine_version.default.engine + master_username = "tfacctest" + master_password = "avoid-plaintext-passwords" + storage_encrypted = true + skip_final_snapshot = true +} + +resource "aws_rds_cluster_instance" "test" { + identifier = "%[3]s-primary" + cluster_identifier = aws_rds_cluster.test.id + instance_class = data.aws_rds_orderable_db_instance.test.instance_class + engine = aws_rds_cluster.test.engine + engine_version = aws_rds_cluster.test.engine_version +} + +resource "aws_kms_key" "test" { + provider = "awsalternate" + + description = %[3]q + + policy = < Date: Wed, 27 Nov 2024 16:27:30 -0600 Subject: [PATCH 3/8] chore: linter --- internal/service/rds/cluster_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/rds/cluster_test.go b/internal/service/rds/cluster_test.go index 5a00c6d610f..7f4993c821f 100644 --- a/internal/service/rds/cluster_test.go +++ b/internal/service/rds/cluster_test.go @@ -4535,13 +4535,13 @@ func testAccClusterConfig_replicationSource_promote(rName string) string { resource "aws_rds_cluster" "alternate" { provider = "awsalternate" - cluster_identifier = "%[1]s-replica" - db_subnet_group_name = aws_db_subnet_group.test.name - engine = %[2]q - kms_key_id = aws_kms_key.test.arn - storage_encrypted = true - skip_final_snapshot = true - source_region = data.aws_region.current.name + cluster_identifier = "%[1]s-replica" + db_subnet_group_name = aws_db_subnet_group.test.name + engine = %[2]q + kms_key_id = aws_kms_key.test.arn + storage_encrypted = true + skip_final_snapshot = true + source_region = data.aws_region.current.name depends_on = [ aws_rds_cluster_instance.test, From dab506f59bb881f02a88df186c9bbc7112e60d4b Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 27 Nov 2024 16:38:06 -0600 Subject: [PATCH 4/8] add CHANGELOG entry --- .changelog/40337.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/40337.txt diff --git a/.changelog/40337.txt b/.changelog/40337.txt new file mode 100644 index 00000000000..428a7097145 --- /dev/null +++ b/.changelog/40337.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_rds_cluster: Add ability to promote read replica cluster to standalone +``` \ No newline at end of file From d55fa55e2e40b2c7812feee2cece0634eba46ba2 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Wed, 27 Nov 2024 16:47:15 -0600 Subject: [PATCH 5/8] chore: linter --- internal/service/rds/cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/rds/cluster.go b/internal/service/rds/cluster.go index 4ebc785f68c..91e19ef8853 100644 --- a/internal/service/rds/cluster.go +++ b/internal/service/rds/cluster.go @@ -1937,7 +1937,7 @@ func statusDBCluster(ctx context.Context, conn *rds.Client, id string, waitNoPen } } -func waitDBClusterAvailable(ctx context.Context, conn *rds.Client, id string, waitNoPendingModifiedValues bool, timeout time.Duration) (*types.DBCluster, error) { +func waitDBClusterAvailable(ctx context.Context, conn *rds.Client, id string, waitNoPendingModifiedValues bool, timeout time.Duration) (*types.DBCluster, error) { //nolint:unparam pendingStatuses := []string{ clusterStatusCreating, clusterStatusMigrating, From 9a9c0632809a94011eb628b335fc64f4c7858d38 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Mon, 2 Dec 2024 09:12:34 -0600 Subject: [PATCH 6/8] aws_rds_cluster: update documentation for new functionality --- website/docs/r/rds_cluster.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index dec747b3720..c3500d9a86f 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -252,7 +252,7 @@ This resource supports the following arguments: * `port` - (Optional) Port on which the DB accepts connections. * `preferred_backup_window` - (Optional) Daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter.Time in UTC. Default: A 30-minute window selected at random from an 8-hour block of time per region, e.g. `04:00-09:00`. * `preferred_maintenance_window` - (Optional) Weekly time range during which system maintenance can occur, in (UTC) e.g., `wed:04:00-wed:04:30` -* `replication_source_identifier` - (Optional) ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica. If DB Cluster is part of a Global Cluster, use the [`lifecycle` configuration block `ignore_changes` argument](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. +* `replication_source_identifier` - (Optional) ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica. **Note:** Removing this attribute after creation will promote the read replica to a standalone cluster.If DB Cluster is part of a Global Cluster, use the [`lifecycle` configuration block `ignore_changes` argument](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. * `restore_to_point_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-pitr.html). More details below. * `scaling_configuration` - (Optional) Nested attribute with scaling properties. Only valid when `engine_mode` is set to `serverless`. More details below. * `serverlessv2_scaling_configuration`- (Optional) Nested attribute with scaling properties for ServerlessV2. Only valid when `engine_mode` is set to `provisioned`. More details below. From 7ebbd8c32ff79418499c19e0b2f6ead7067a5b4e Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Mon, 2 Dec 2024 09:37:16 -0600 Subject: [PATCH 7/8] chore: linter --- internal/service/rds/cluster_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/rds/cluster_test.go b/internal/service/rds/cluster_test.go index 9710cfcd3ac..76594e07322 100644 --- a/internal/service/rds/cluster_test.go +++ b/internal/service/rds/cluster_test.go @@ -1319,16 +1319,16 @@ func TestAccRDSCluster_ReplicationSourceIdentifier_promote(t *testing.T) { { Config: testAccClusterConfig_replicationSource_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(acctest.Region(), &providers)), - testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(acctest.AlternateRegion(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(ctx, acctest.Region(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(ctx, acctest.AlternateRegion(), &providers)), resource.TestCheckResourceAttrSet(resourceName2, "replication_source_identifier"), ), }, { Config: testAccClusterConfig_replicationSource_promote(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(acctest.Region(), &providers)), - testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(acctest.AlternateRegion(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName, &primaryCluster, acctest.RegionProviderFunc(ctx, acctest.Region(), &providers)), + testAccCheckClusterExistsWithProvider(ctx, resourceName2, &replicaCluster, acctest.RegionProviderFunc(ctx, acctest.AlternateRegion(), &providers)), resource.TestCheckResourceAttr(resourceName2, "replication_source_identifier", ""), ), }, From 2e844863b2fb005619e1b7dd712acbc80d5c6d50 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Mon, 2 Dec 2024 18:24:24 -0600 Subject: [PATCH 8/8] aws_rds_cluster: add space to documentation --- website/docs/r/rds_cluster.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index c3500d9a86f..fb0a8dfa083 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -252,7 +252,7 @@ This resource supports the following arguments: * `port` - (Optional) Port on which the DB accepts connections. * `preferred_backup_window` - (Optional) Daily time range during which automated backups are created if automated backups are enabled using the BackupRetentionPeriod parameter.Time in UTC. Default: A 30-minute window selected at random from an 8-hour block of time per region, e.g. `04:00-09:00`. * `preferred_maintenance_window` - (Optional) Weekly time range during which system maintenance can occur, in (UTC) e.g., `wed:04:00-wed:04:30` -* `replication_source_identifier` - (Optional) ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica. **Note:** Removing this attribute after creation will promote the read replica to a standalone cluster.If DB Cluster is part of a Global Cluster, use the [`lifecycle` configuration block `ignore_changes` argument](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. +* `replication_source_identifier` - (Optional) ARN of a source DB cluster or DB instance if this DB cluster is to be created as a Read Replica. **Note:** Removing this attribute after creation will promote the read replica to a standalone cluster. If DB Cluster is part of a Global Cluster, use the [`lifecycle` configuration block `ignore_changes` argument](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. * `restore_to_point_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-pitr.html). More details below. * `scaling_configuration` - (Optional) Nested attribute with scaling properties. Only valid when `engine_mode` is set to `serverless`. More details below. * `serverlessv2_scaling_configuration`- (Optional) Nested attribute with scaling properties for ServerlessV2. Only valid when `engine_mode` is set to `provisioned`. More details below.