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

r/aws_rds_cluster_instance: fix destroy error when cluster instance is in a read replica #40409

Merged
merged 6 commits into from
Dec 4, 2024
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/40409.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_rds_cluster_instance: Fix error when destroying from a read replica cluster
```
20 changes: 20 additions & 0 deletions internal/service/rds/cluster_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ func resourceClusterInstance() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
names.AttrForceDestroy: {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
names.AttrIdentifier: {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -543,6 +548,21 @@ func resourceClusterInstanceDelete(ctx context.Context, d *schema.ResourceData,
},
"Delete the replica cluster before deleting")

if errs.IsAErrorMessageContains[*types.InvalidDBClusterStateFault](err, "Cannot delete the last instance of the read replica DB cluster") && d.Get(names.AttrForceDestroy).(bool) {
_, err = conn.PromoteReadReplicaDBCluster(ctx, &rds.PromoteReadReplicaDBClusterInput{
DBClusterIdentifier: aws.String(d.Get(names.AttrClusterIdentifier).(string)),
})
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.TimeoutDelete)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for RDS Cluster (%s) update: %s", d.Id(), err)
}

_, err = conn.DeleteDBInstance(ctx, input)
}

if errs.IsA[*types.DBInstanceNotFoundFault](err) {
return diags
}
Expand Down
139 changes: 139 additions & 0 deletions internal/service/rds/cluster_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/rds"
"github.com/aws/aws-sdk-go-v2/service/rds/types"
"github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
Expand Down Expand Up @@ -65,6 +66,7 @@ func TestAccRDSClusterInstance_basic(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -136,6 +138,7 @@ func TestAccRDSClusterInstance_identifierGenerated(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -172,6 +175,7 @@ func TestAccRDSClusterInstance_identifierPrefix(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -208,6 +212,7 @@ func TestAccRDSClusterInstance_tags(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -303,6 +308,7 @@ func TestAccRDSClusterInstance_az(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -339,6 +345,7 @@ func TestAccRDSClusterInstance_kmsKey(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -374,6 +381,7 @@ func TestAccRDSClusterInstance_publiclyAccessible(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand All @@ -389,6 +397,7 @@ func TestAccRDSClusterInstance_publiclyAccessible(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -431,6 +440,7 @@ func TestAccRDSClusterInstance_copyTagsToSnapshot(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -474,6 +484,7 @@ func TestAccRDSClusterInstance_caCertificateIdentifier(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -509,6 +520,7 @@ func TestAccRDSClusterInstance_monitoringInterval(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -566,6 +578,7 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToDisabled(t *testing.T)
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -609,6 +622,7 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToRemoved(t *testing.T)
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -650,6 +664,7 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_removedToEnabled(t *testing.T)
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -693,6 +708,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraMySQL1(t *testin
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -729,6 +745,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraPostgresql(t *te
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -767,6 +784,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraMySQL1(t *testi
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -803,6 +821,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraMySQL1_defaultKe
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -846,6 +865,7 @@ func TestAccRDSClusterInstance_performanceInsightsRetentionPeriod(t *testing.T)
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand Down Expand Up @@ -900,6 +920,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraPostgresql(t *t
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
},
Expand Down Expand Up @@ -936,6 +957,7 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraPostgresql_defau
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{
names.AttrApplyImmediately,
names.AttrForceDestroy,
},
},
{
Expand All @@ -946,6 +968,88 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraPostgresql_defau
})
}

func TestAccRDSClusterInstance_Replica_basic(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var primaryInstance types.DBInstance
var replicaInstance types.DBInstance
resourceName := "aws_rds_cluster_instance.test"
resourceName2 := "aws_rds_cluster_instance.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(testAccCheckClusterInstanceDestroyWithProvider(ctx), &providers),
Steps: []resource.TestStep{
{
Config: testAccClusterInstanceConfig_replicationSource_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckClusterInstanceExistsWithProvider(ctx, resourceName, &primaryInstance, acctest.RegionProviderFunc(ctx, acctest.Region(), &providers)),
testAccCheckClusterInstanceExistsWithProvider(ctx, resourceName2, &replicaInstance, acctest.RegionProviderFunc(ctx, acctest.AlternateRegion(), &providers)),
),
},
},
})
}

func testAccCheckClusterInstanceExistsWithProvider(ctx context.Context, n string, v *types.DBInstance, providerF func() *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

conn := providerF().Meta().(*conns.AWSClient).RDSClient(ctx)

output, err := tfrds.FindDBInstanceByID(ctx, conn, rs.Primary.ID)
if err != nil {
return err
}

*v = *output

return nil
}
}

func testAccCheckClusterInstanceDestroyWithProvider(ctx context.Context) acctest.TestCheckWithProviderFunc {
return func(s *terraform.State, provider *schema.Provider) error {
conn := provider.Meta().(*conns.AWSClient).RDSClient(ctx)

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_rds_cluster_instance" {
continue
}

_, err := tfrds.FindDBInstanceByID(ctx, conn, rs.Primary.ID)

if tfresource.NotFound(err) {
continue
}

if err != nil {
return err
}

return fmt.Errorf("RDS Cluster Instance %s still exists", rs.Primary.ID)
}

return nil
}
}

func testAccPerformanceInsightsDefaultVersionPreCheck(ctx context.Context, t *testing.T, engine string) {
conn := acctest.Provider.Meta().(*conns.AWSClient).RDSClient(ctx)

Expand Down Expand Up @@ -1545,6 +1649,41 @@ resource "aws_rds_cluster_instance" "test" {
`, rName))
}

func testAccClusterInstanceConfig_replicationSource_basic(rName string) string {
return acctest.ConfigCompose(
testAccClusterConfig_replicationSource_base(rName),
fmt.Sprintf(`
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
replication_source_identifier = aws_rds_cluster.test.arn
source_region = data.aws_region.current.name

depends_on = [
aws_rds_cluster_instance.test,
]
}

resource "aws_rds_cluster_instance" "alternate" {
provider = "awsalternate"

identifier = "%[1]s-replica"
cluster_identifier = aws_rds_cluster.alternate.id
instance_class = data.aws_rds_orderable_db_instance.test.instance_class
engine = aws_rds_cluster.alternate.engine
engine_version = aws_rds_cluster.alternate.engine_version

force_destroy = true
}
`, rName, tfrds.ClusterEngineAuroraMySQL))
}

func testAccClusterInstanceConfig_performanceInsightsEnabledAuroraPostgresql(rName, engine string) string {
return acctest.ConfigCompose(
testAccClusterInstanceConfig_orderableEngineBase(engine, true),
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/rds_cluster_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ This resource supports the following arguments:
* `engine_version` - (Optional) Database engine version. Please note that to upgrade the `engine_version` of the instance, it must be done on the `aws_rds_cluster` `engine_version`. Trying to upgrade in `aws_cluster_instance` will not update the `engine_version`.
* `engine` - (Required, Forces new resource) Name of the database engine to be used for the RDS cluster instance.
Valid Values: `aurora-mysql`, `aurora-postgresql`, `mysql`, `postgres`.(Note that `mysql` and `postgres` are Multi-AZ RDS clusters).
* `force_destroy` - (Optional) Forces an instance to be destroyed when a part of a read replica cluster. **Note:** will promote the read replica to a standalone cluster before instance deletion.
* `identifier_prefix` - (Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Conflicts with `identifier`.
* `identifier` - (Optional, Forces new resource) Identifier for the RDS instance, if omitted, Terraform will assign a random, unique identifier.
* `instance_class` - (Required) Instance class to use. For details on CPU and memory, see [Scaling Aurora DB Instances][4]. Aurora uses `db.*` instance classes/types. Please see [AWS Documentation][7] for currently available instance classes and complete details. For Aurora Serverless v2 use `db.serverless`.
Expand Down
Loading