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/rds_role_association: wait for cluster/instance to be available #39457

Merged
merged 6 commits into from
Sep 24, 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
7 changes: 7 additions & 0 deletions .changelog/39457.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:bug
resource/aws_rds_cluster_role_association: Fix intermittent failure when cluster is not in an available state
```

```release-note:bug
resource/aws_db_instance_role_association: Fix intermittent failure when instance is not in an available state
```
34 changes: 34 additions & 0 deletions internal/service/rds/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -1919,6 +1919,40 @@ 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) {
pendingStatuses := []string{
clusterStatusCreating,
clusterStatusMigrating,
clusterStatusPreparingDataMigration,
clusterStatusRebooting,
clusterStatusBackingUp,
clusterStatusConfiguringIAMDatabaseAuth,
clusterStatusConfiguringEnhancedMonitoring,
clusterStatusModifying,
clusterStatusRenaming,
clusterStatusResettingMasterCredentials,
clusterStatusScalingCompute,
clusterStatusUpgrading,
}

stateConf := &retry.StateChangeConf{
Pending: pendingStatuses,
Target: []string{clusterStatusAvailable},
Refresh: statusDBCluster(ctx, conn, id, waitNoPendingModifiedValues),
Timeout: timeout,
MinTimeout: 10 * time.Second,
Delay: 30 * time.Second,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*types.DBCluster); ok {
return output, err
}

return nil, err
}

func waitDBClusterCreated(ctx context.Context, conn *rds.Client, id string, timeout time.Duration) (*types.DBCluster, error) {
stateConf := &retry.StateChangeConf{
Pending: []string{
Expand Down
20 changes: 17 additions & 3 deletions internal/service/rds/cluster_role_association.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/aws"
"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/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -75,9 +76,22 @@ func resourceClusterRoleAssociationCreate(ctx context.Context, d *schema.Resourc
RoleArn: aws.String(roleARN),
}

_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {
return conn.AddRoleToDBCluster(ctx, input)
}, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions")
_, err := conn.AddRoleToDBCluster(ctx, input)

// check if the cluster is in a valid state to add the role association
if errs.IsA[*types.InvalidDBClusterStateFault](err) {
if _, err := waitDBClusterAvailable(ctx, conn, dbClusterID, true, d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for RDS Cluster (%s) available: %s", dbClusterID, err)
}

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

if tfawserr.ErrMessageContains(err, errCodeInvalidParameterValue, errIAMRolePropagationMessage) {
_, err = tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {
return conn.AddRoleToDBCluster(ctx, input)
}, errCodeInvalidParameterValue, errIAMRolePropagationMessage)
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating RDS Cluster IAM Role Association (%s): %s", id, err)
Expand Down
4 changes: 4 additions & 0 deletions internal/service/rds/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ const (
errCodeInvalidParameterValue = "InvalidParameterValue"
errCodeValidationError = "ValidationError"
)

const (
errIAMRolePropagationMessage = "IAM role ARN value is invalid or does not include the required permissions"
)
35 changes: 24 additions & 11 deletions internal/service/rds/instance_role_association.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/aws"
"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/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -42,6 +43,11 @@ func resourceInstanceRoleAssociation() *schema.Resource {
StateContext: schema.ImportStatePassthroughContext,
},

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(10 * time.Minute),
},

Schema: map[string]*schema.Schema{
"db_instance_identifier": {
Type: schema.TypeString,
Expand Down Expand Up @@ -76,20 +82,30 @@ func resourceInstanceRoleAssociationCreate(ctx context.Context, d *schema.Resour
RoleArn: aws.String(roleARN),
}

_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {
return conn.AddRoleToDBInstance(ctx, input)
}, errCodeInvalidParameterValue, "IAM role ARN value is invalid or does not include the required permissions")
_, err := conn.AddRoleToDBInstance(ctx, input)

// check if the instance is in a valid state to add the role association
if errs.IsA[*types.InvalidDBInstanceStateFault](err) {
if _, err := waitDBInstanceAvailable(ctx, conn, dbInstanceIdentifier, d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Instance (%s) available: %s", dbInstanceIdentifier, err)
}

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

if tfawserr.ErrMessageContains(err, errCodeInvalidParameterValue, errIAMRolePropagationMessage) {
_, err = tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {
return conn.AddRoleToDBInstance(ctx, input)
}, errCodeInvalidParameterValue, errIAMRolePropagationMessage)
}

if err != nil {
return sdkdiag.AppendErrorf(diags, "creating RDS DB Instance IAM Role Association (%s): %s", id, err)
}

d.SetId(id)

const (
timeout = 10 * time.Minute
)
if _, err := waitDBInstanceRoleAssociationCreated(ctx, conn, dbInstanceIdentifier, roleARN, timeout); err != nil {
if _, err := waitDBInstanceRoleAssociationCreated(ctx, conn, dbInstanceIdentifier, roleARN, d.Timeout(schema.TimeoutCreate)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Instance IAM Role Association (%s) create: %s", d.Id(), err)
}

Expand Down Expand Up @@ -148,10 +164,7 @@ func resourceInstanceRoleAssociationDelete(ctx context.Context, d *schema.Resour
return sdkdiag.AppendErrorf(diags, "deleting RDS DB Instance IAM Role Association (%s): %s", d.Id(), err)
}

const (
timeout = 10 * time.Minute
)
if _, err := waitDBInstanceRoleAssociationDeleted(ctx, conn, dbInstanceIdentifier, roleARN, timeout); err != nil {
if _, err := waitDBInstanceRoleAssociationDeleted(ctx, conn, dbInstanceIdentifier, roleARN, d.Timeout(schema.TimeoutDelete)); err != nil {
return sdkdiag.AppendErrorf(diags, "waiting for RDS DB Instance IAM Role Association (%s) delete: %s", d.Id(), err)
}

Expand Down
7 changes: 7 additions & 0 deletions website/docs/r/db_instance_role_association.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ This resource exports the following attributes in addition to the arguments abov

* `id` - DB Instance Identifier and IAM Role ARN separated by a comma (`,`)

## Timeouts

[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts):

- `create` - (Default `10m`)
- `delete` - (Default `10m`)

## Import

In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import `aws_db_instance_role_association` using the DB Instance Identifier and IAM Role ARN separated by a comma (`,`). For example:
Expand Down
Loading