Skip to content

Commit

Permalink
Merge pull request #32846 from AdamTylerLynch/e-rds_custom_for_oracle
Browse files Browse the repository at this point in the history
RDS Custom enhancements
  • Loading branch information
ewbankkit authored Aug 7, 2023
2 parents 1c429dc + 9f5e1de commit 12b7deb
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 32 deletions.
15 changes: 15 additions & 0 deletions .changelog/32846.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:enhancement
resource/aws_rds_cluster_instance: Add `custom_iam_instance_profile` argument to allow RDS Custom users to specify an IAM Instance Profile for the RDS Cluster Instance
```

```release-note:enhancement
resource/aws_rds_cluster_instance: Update `engine` plan-time validation to allow for RDS Custom engine types
```

```release-note:enhancement
resource/aws_rds_cluster: Add `db_system_id` argument to support RDS Custom engine types
```

```release-note:enhancement
data-source/aws_rds_cluster: Add `db_system_id` attribute
```
80 changes: 59 additions & 21 deletions internal/service/rds/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
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"
Expand Down Expand Up @@ -150,6 +151,12 @@ func ResourceCluster() *schema.Resource {
ForceNew: true,
Computed: true,
},
"db_system_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"deletion_protection": {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -177,10 +184,13 @@ func ResourceCluster() *schema.Resource {
Computed: true,
},
"engine": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validClusterEngine(),
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.Any(
validation.StringMatch(regexp.MustCompile(fmt.Sprintf(`^%s.*$`, InstanceEngineCustomPrefix)), fmt.Sprintf("must begin with %s", InstanceEngineCustomPrefix)),
validation.StringInSlice(ClusterEngine_Values(), false),
),
},
"engine_mode": {
Type: schema.TypeString,
Expand Down Expand Up @@ -941,6 +951,10 @@ func resourceClusterCreate(ctx context.Context, d *schema.ResourceData, meta int
input.DBSubnetGroupName = aws.String(v.(string))
}

if v, ok := d.GetOk("db_system_id"); ok {
input.DBSystemId = aws.String(v.(string))
}

if v, ok := d.GetOk("enable_global_write_forwarding"); ok {
input.EnableGlobalWriteForwarding = aws.Bool(v.(bool))
}
Expand Down Expand Up @@ -1116,6 +1130,7 @@ func resourceClusterRead(ctx context.Context, d *schema.ResourceData, meta inter
d.Set("db_cluster_instance_class", dbc.DBClusterInstanceClass)
d.Set("db_cluster_parameter_group_name", dbc.DBClusterParameterGroup)
d.Set("db_subnet_group_name", dbc.DBSubnetGroup)
d.Set("db_system_id", dbc.DBSystemId)
d.Set("deletion_protection", dbc.DeletionProtection)
d.Set("enabled_cloudwatch_logs_exports", aws.StringValueSlice(dbc.EnabledCloudwatchLogsExports))
d.Set("enable_http_endpoint", dbc.HttpEndpointEnabled)
Expand Down Expand Up @@ -1586,44 +1601,67 @@ func FindDBClusterByID(ctx context.Context, conn *rds.RDS, id string) (*rds.DBCl
input := &rds.DescribeDBClustersInput{
DBClusterIdentifier: aws.String(id),
}
output, err := findDBCluster(ctx, conn, input)

output, err := conn.DescribeDBClustersWithContext(ctx, input)
if err != nil {
return nil, err
}

if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBClusterNotFoundFault) {
// Eventual consistency check.
if arn.IsARN(id) {
if aws.StringValue(output.DBClusterArn) != id {
return nil, &retry.NotFoundError{
LastRequest: input,
}
}
} else if aws.StringValue(output.DBClusterIdentifier) != id {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

return output, nil
}

func findDBCluster(ctx context.Context, conn *rds.RDS, input *rds.DescribeDBClustersInput) (*rds.DBCluster, error) {
output, err := findDBClusters(ctx, conn, input, tfslices.PredicateTrue[*rds.DBCluster]())

if err != nil {
return nil, err
}

if output == nil || len(output.DBClusters) == 0 || output.DBClusters[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}
return tfresource.AssertSinglePtrResult(output)
}

if count := len(output.DBClusters); count > 1 {
return nil, tfresource.NewTooManyResultsError(count, input)
}
func findDBClusters(ctx context.Context, conn *rds.RDS, input *rds.DescribeDBClustersInput, filter tfslices.Predicate[*rds.DBCluster]) ([]*rds.DBCluster, error) {
var output []*rds.DBCluster

dbCluster := output.DBClusters[0]
err := conn.DescribeDBClustersPagesWithContext(ctx, input, func(page *rds.DescribeDBClustersOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

// Eventual consistency check.
if arn.IsARN(id) {
if aws.StringValue(dbCluster.DBClusterArn) != id {
return nil, &retry.NotFoundError{
LastRequest: input,
for _, v := range page.DBClusters {
if v != nil && filter(v) {
output = append(output, v)
}
}
} else if aws.StringValue(dbCluster.DBClusterIdentifier) != id {

return !lastPage
})

if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBClusterNotFoundFault) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

return dbCluster, nil
if err != nil {
return nil, err
}

return output, nil
}

func statusDBCluster(ctx context.Context, conn *rds.RDS, id string) retry.StateRefreshFunc {
Expand Down
5 changes: 5 additions & 0 deletions internal/service/rds/cluster_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func DataSourceCluster() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"db_system_id": {
Type: schema.TypeString,
Computed: true,
},
"enabled_cloudwatch_logs_exports": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -201,6 +205,7 @@ func dataSourceClusterRead(ctx context.Context, d *schema.ResourceData, meta int
}
d.Set("db_cluster_parameter_group_name", dbc.DBClusterParameterGroup)
d.Set("db_subnet_group_name", dbc.DBSubnetGroup)
d.Set("db_system_id", dbc.DBSystemId)
d.Set("enabled_cloudwatch_logs_exports", aws.StringValueSlice(dbc.EnabledCloudwatchLogsExports))
d.Set("endpoint", dbc.Endpoint)
d.Set("engine", dbc.Engine)
Expand Down
1 change: 1 addition & 0 deletions internal/service/rds/cluster_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestAccRDSClusterDataSource_basic(t *testing.T) {
resource.TestCheckResourceAttrPair(dataSourceName, "database_name", resourceName, "database_name"),
resource.TestCheckResourceAttrPair(dataSourceName, "db_cluster_parameter_group_name", resourceName, "db_cluster_parameter_group_name"),
resource.TestCheckResourceAttrPair(dataSourceName, "db_subnet_group_name", resourceName, "db_subnet_group_name"),
resource.TestCheckResourceAttrPair(dataSourceName, "db_system_id", resourceName, "db_system_id"),
resource.TestCheckResourceAttrPair(dataSourceName, "engine", resourceName, "engine"),
resource.TestCheckResourceAttrPair(dataSourceName, "engine_mode", resourceName, "engine_mode"),
resource.TestCheckResourceAttrPair(dataSourceName, "engine_version", resourceName, "engine_version"),
Expand Down
30 changes: 26 additions & 4 deletions internal/service/rds/cluster_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ package rds

import (
"context"
"fmt"
"log"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -81,6 +83,12 @@ func ResourceClusterInstance() *schema.Resource {
Optional: true,
Default: false,
},
"custom_iam_instance_profile": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^AWSRDSCustom.*$`), "must begin with AWSRDSCustom"),
},
"db_parameter_group_name": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -101,10 +109,13 @@ func ResourceClusterInstance() *schema.Resource {
Computed: true,
},
"engine": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validClusterEngine(),
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.Any(
validation.StringMatch(regexp.MustCompile(fmt.Sprintf(`^%s.*$`, InstanceEngineCustomPrefix)), fmt.Sprintf("must begin with %s", InstanceEngineCustomPrefix)),
validation.StringInSlice(ClusterEngine_Values(), false),
),
},
"engine_version": {
Type: schema.TypeString,
Expand Down Expand Up @@ -265,6 +276,10 @@ func resourceClusterInstanceCreate(ctx context.Context, d *schema.ResourceData,
input.DBSubnetGroupName = aws.String(v.(string))
}

if v, ok := d.GetOk("custom_iam_instance_profile"); ok {
input.CustomIamInstanceProfile = aws.String(v.(string))
}

if v, ok := d.GetOk("engine_version"); ok {
input.EngineVersion = aws.String(v.(string))
}
Expand Down Expand Up @@ -391,6 +406,7 @@ func resourceClusterInstanceRead(ctx context.Context, d *schema.ResourceData, me
d.Set("ca_cert_identifier", db.CACertificateIdentifier)
d.Set("cluster_identifier", db.DBClusterIdentifier)
d.Set("copy_tags_to_snapshot", db.CopyTagsToSnapshot)
d.Set("custom_iam_instance_profile", db.CustomIamInstanceProfile)
if len(db.DBParameterGroups) > 0 && db.DBParameterGroups[0] != nil {
d.Set("db_parameter_group_name", db.DBParameterGroups[0].DBParameterGroupName)
}
Expand Down Expand Up @@ -512,6 +528,12 @@ func resourceClusterInstanceDelete(ctx context.Context, d *schema.ResourceData,
DBInstanceIdentifier: aws.String(d.Id()),
}

// Automatically set skip_final_snapshot = true for RDS Custom instances
if strings.HasPrefix(d.Get("engine").(string), InstanceEngineCustomPrefix) {
log.Printf("[DEBUG] RDS Custom engine detected (%s) applying SkipFinalSnapshot: %s", d.Get("engine").(string), "true")
input.SkipFinalSnapshot = aws.Bool(true)
}

log.Printf("[DEBUG] Deleting RDS Cluster Instance: %s", d.Id())
_, err := tfresource.RetryWhenAWSErrMessageContains(ctx, d.Timeout(schema.TimeoutDelete),
func() (interface{}, error) {
Expand Down
1 change: 1 addition & 0 deletions internal/service/rds/cluster_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestAccRDSClusterInstance_basic(t *testing.T) {
resource.TestCheckResourceAttrSet(resourceName, "availability_zone"),
resource.TestCheckResourceAttr(resourceName, "cluster_identifier", rName),
resource.TestCheckResourceAttr(resourceName, "copy_tags_to_snapshot", "false"),
resource.TestCheckResourceAttr(resourceName, "custom_iam_instance_profile", ""),
resource.TestCheckResourceAttrSet(resourceName, "dbi_resource_id"),
resource.TestCheckResourceAttr(resourceName, "engine", "aurora-mysql"),
resource.TestCheckResourceAttrSet(resourceName, "engine_version"),
Expand Down
1 change: 1 addition & 0 deletions internal/service/rds/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func TestAccRDSCluster_basic(t *testing.T) {
resource.TestCheckResourceAttrSet(resourceName, "cluster_resource_id"),
resource.TestCheckResourceAttr(resourceName, "copy_tags_to_snapshot", "false"),
resource.TestCheckResourceAttr(resourceName, "db_cluster_parameter_group_name", "default.aurora-mysql5.7"),
resource.TestCheckResourceAttr(resourceName, "db_system_id", ""),
resource.TestCheckResourceAttr(resourceName, "enabled_cloudwatch_logs_exports.#", "0"),
resource.TestCheckResourceAttr(resourceName, "engine", "aurora-mysql"),
resource.TestCheckResourceAttrSet(resourceName, "engine_version"),
Expand Down
3 changes: 3 additions & 0 deletions internal/service/rds/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const (
InstanceEngineSQLServerExpress = "sqlserver-ex"
InstanceEngineSQLServerStandard = "sqlserver-se"
InstanceEngineSQLServerWeb = "sqlserver-ewb"
InstanceEngineCustomPrefix = "custom-"
)

// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/accessing-monitoring.html#Overview.DBInstance.Status.
Expand All @@ -80,6 +81,7 @@ const (
InstanceStatusFailed = "failed"
InstanceStatusInaccessibleEncryptionCredentials = "inaccessible-encryption-credentials"
InstanceStatusInaccessibleEncryptionCredentialsRecoverable = "inaccessible-encryption-credentials-recoverable"
InstanceStatusIncompatiblCreate = "incompatible-create"
InstanceStatusIncompatibleNetwork = "incompatible-network"
InstanceStatusIncompatibleOptionGroup = "incompatible-option-group"
InstanceStatusIncompatibleParameters = "incompatible-parameters"
Expand Down Expand Up @@ -117,6 +119,7 @@ const (
ClusterEngineAuroraPostgreSQL = "aurora-postgresql"
ClusterEngineMySQL = "mysql"
ClusterEnginePostgres = "postgres"
ClusterEngineCustomPrefix = "custom-"
)

func ClusterEngine_Values() []string {
Expand Down
7 changes: 0 additions & 7 deletions internal/service/rds/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ package rds
import (
"fmt"
"regexp"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func validEventSubscriptionName(v interface{}, k string) (ws []string, errors []error) {
Expand Down Expand Up @@ -146,10 +143,6 @@ func validSubnetGroupNamePrefix(v interface{}, k string) (ws []string, errors []
return
}

func validClusterEngine() schema.SchemaValidateFunc {
return validation.StringInSlice(ClusterEngine_Values(), false)
}

func validIdentifier(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/rds_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ This argument supports the following arguments:
* `db_cluster_instance_class` - (Optional) (Required for Multi-AZ DB cluster) The compute and memory capacity of each DB instance in the Multi-AZ DB cluster, for example db.m6g.xlarge. Not all DB instance classes are available in all AWS Regions, or for all database engines. For the full list of DB instance classes and availability for your engine, see [DB instance class](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html) in the Amazon RDS User Guide.
* `db_instance_parameter_group_name` - (Optional) Instance parameter group to associate with all instances of the DB cluster. The `db_instance_parameter_group_name` parameter is only valid in combination with the `allow_major_version_upgrade` parameter.
* `db_subnet_group_name` - (Optional) DB subnet group to associate with this DB instance. **NOTE:** This must match the `db_subnet_group_name` specified on every [`aws_rds_cluster_instance`](/docs/providers/aws/r/rds_cluster_instance.html) in the cluster.
* `db_system_id` - (Optional) For use with RDS Custom.
* `deletion_protection` - (Optional) If the DB instance should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`.
* `enable_global_write_forwarding` - (Optional) Whether cluster should forward writes to an associated global cluster. Applied to secondary clusters to enable them to forward writes to an [`aws_rds_global_cluster`](/docs/providers/aws/r/rds_global_cluster.html)'s primary cluster. See the [Aurora Userguide documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database-write-forwarding.html) for more information.
* `enable_http_endpoint` - (Optional) Enable HTTP endpoint (data API). Only valid when `engine_mode` is set to `serverless`.
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 @@ -59,6 +59,7 @@ This argument supports the following arguments:
* `ca_cert_identifier` - (Optional) Identifier of the CA certificate for the DB instance.
* `cluster_identifier` - (Required, Forces new resource) Identifier of the [`aws_rds_cluster`](/docs/providers/aws/r/rds_cluster.html) in which to launch this instance.
* `copy_tags_to_snapshot` – (Optional, boolean) Indicates whether to copy all of the user-defined tags from the DB instance to snapshots of the DB instance. Default `false`.
* `custom_iam_instance_profile` - (Optional) Instance profile associated with the underlying Amazon EC2 instance of an RDS Custom DB instance.
* `db_parameter_group_name` - (Optional) Name of the DB parameter group to associate with this instance.
* `db_subnet_group_name` - (Required if `publicly_accessible = false`, Optional otherwise, Forces new resource) DB subnet group to associate with this DB instance. **NOTE:** This must match the `db_subnet_group_name` of the attached [`aws_rds_cluster`](/docs/providers/aws/r/rds_cluster.html).
* `engine_version` - (Optional) Database engine version.
Expand Down

0 comments on commit 12b7deb

Please sign in to comment.