diff --git a/.changelog/24363.txt b/.changelog/24363.txt new file mode 100644 index 000000000000..0c4a3e4c51a6 --- /dev/null +++ b/.changelog/24363.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_rds_cluster: Add `serverlessv2_scaling_configuration` argument to support [Aurora Serverless v2](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.html) +``` diff --git a/docs/contributing/data-handling-and-conversion.md b/docs/contributing/data-handling-and-conversion.md index 0f97e5a5a2ec..a8d2d410483b 100644 --- a/docs/contributing/data-handling-and-conversion.md +++ b/docs/contributing/data-handling-and-conversion.md @@ -529,8 +529,8 @@ To read: func expandStructure(tfMap map[string]interface{}) *service.Structure { // ... - if v, ok := tfMap["nested_attribute_name"].(int); ok && v != 0.0 { - apiObject.NestedAttributeName = aws.Float64(float64(v)) + if v, ok := tfMap["nested_attribute_name"].(float64); ok && v != 0.0 { + apiObject.NestedAttributeName = aws.Float64(v) } // ... diff --git a/internal/service/rds/cluster.go b/internal/service/rds/cluster.go index 71283cfd69f0..a142cde3349d 100644 --- a/internal/service/rds/cluster.go +++ b/internal/service/rds/cluster.go @@ -34,6 +34,7 @@ func ResourceCluster() *schema.Resource { Read: resourceClusterRead, Update: resourceClusterUpdate, Delete: resourceClusterDelete, + Importer: &schema.ResourceImporter{ State: resourceClusterImport, }, @@ -45,232 +46,258 @@ func ResourceCluster() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, + "allocated_storage": { + Type: schema.TypeInt, + Optional: true, Computed: true, }, - "allow_major_version_upgrade": { Type: schema.TypeBool, Optional: true, }, - + // apply_immediately is used to determine when the update modifications take place. + // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html + "apply_immediately": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, "availability_zones": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, ForceNew: true, Computed: true, - Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "backup_retention_period": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntAtMost(35), }, - "backtrack_window": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(0, 259200), }, - "cluster_identifier": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, - ConflictsWith: []string{"cluster_identifier_prefix"}, ValidateFunc: validIdentifier, + ConflictsWith: []string{"cluster_identifier_prefix"}, }, "cluster_identifier_prefix": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, - ConflictsWith: []string{"cluster_identifier"}, ValidateFunc: validIdentifierPrefix, + ConflictsWith: []string{"cluster_identifier"}, }, - "cluster_members": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, - Set: schema.HashString, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "cluster_resource_id": { + Type: schema.TypeString, + Computed: true, }, - "copy_tags_to_snapshot": { Type: schema.TypeBool, Optional: true, Default: false, }, - "database_name": { Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, }, - - "db_subnet_group_name": { + "db_cluster_instance_class": { Type: schema.TypeString, Optional: true, - ForceNew: true, - Computed: true, }, - "db_cluster_parameter_group_name": { Type: schema.TypeString, Optional: true, Computed: true, }, - "db_instance_parameter_group_name": { Type: schema.TypeString, Optional: true, }, - - "deletion_protection": { - Type: schema.TypeBool, - Optional: true, - }, - - "endpoint": { + "db_subnet_group_name": { Type: schema.TypeString, + Optional: true, + ForceNew: true, Computed: true, }, - - "global_cluster_identifier": { - Type: schema.TypeString, + "deletion_protection": { + Type: schema.TypeBool, Optional: true, }, - "enable_global_write_forwarding": { Type: schema.TypeBool, Optional: true, Default: false, }, - - "reader_endpoint": { - Type: schema.TypeString, - Computed: true, + "enabled_cloudwatch_logs_exports": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(ClusterExportableLogType_Values(), false), + }, }, - - "hosted_zone_id": { - Type: schema.TypeString, - Computed: true, + "enable_http_endpoint": { + Type: schema.TypeBool, + Optional: true, + Default: false, }, - - "db_cluster_instance_class": { + "endpoint": { Type: schema.TypeString, - Optional: true, + Computed: true, }, - "engine": { Type: schema.TypeString, Optional: true, - Default: "aurora", ForceNew: true, + Default: EngineAurora, ValidateFunc: validEngine(), }, - "engine_mode": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "provisioned", - ValidateFunc: validation.StringInSlice([]string{ - "global", - "multimaster", - "parallelquery", - "provisioned", - "serverless", - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: EngineModeProvisioned, + ValidateFunc: validation.StringInSlice(EngineMode_Values(), false), }, - "engine_version": { Type: schema.TypeString, Optional: true, Computed: true, }, - "engine_version_actual": { Type: schema.TypeString, Computed: true, }, - - "scaling_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "auto_pause": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "max_capacity": { - Type: schema.TypeInt, - Optional: true, - Default: clusterScalingConfiguration_DefaultMaxCapacity, - }, - "min_capacity": { - Type: schema.TypeInt, - Optional: true, - Default: clusterScalingConfiguration_DefaultMinCapacity, - }, - "seconds_until_auto_pause": { - Type: schema.TypeInt, - Optional: true, - Default: 300, - ValidateFunc: validation.IntBetween(300, 86400), - }, - "timeout_action": { - Type: schema.TypeString, - Optional: true, - Default: "RollbackCapacityChange", - ValidateFunc: validation.StringInSlice([]string{ - "ForceApplyCapacityChange", - "RollbackCapacityChange", - }, false), - }, - }, + "final_snapshot_identifier": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { + es = append(es, fmt.Errorf( + "only alphanumeric characters and hyphens allowed in %q", k)) + } + if regexp.MustCompile(`--`).MatchString(value) { + es = append(es, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) + } + if regexp.MustCompile(`-$`).MatchString(value) { + es = append(es, fmt.Errorf("%q cannot end in a hyphen", k)) + } + return }, }, - - "allocated_storage": { - Type: schema.TypeInt, + "global_cluster_identifier": { + Type: schema.TypeString, Optional: true, + }, + "hosted_zone_id": { + Type: schema.TypeString, Computed: true, }, - - "storage_type": { + "iam_database_authentication_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "iam_roles": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "iops": { + Type: schema.TypeInt, + Optional: true, + }, + "kms_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "master_password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "master_username": { Type: schema.TypeString, + Computed: true, Optional: true, ForceNew: true, }, - - "iops": { + "port": { Type: schema.TypeInt, Optional: true, + Computed: true, }, - - "storage_encrypted": { - Type: schema.TypeBool, + "preferred_backup_window": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: verify.ValidOnceADayWindowFormat, + }, + "preferred_maintenance_window": { + Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, + StateFunc: func(val interface{}) string { + if val == nil { + return "" + } + return strings.ToLower(val.(string)) + }, + ValidateFunc: verify.ValidOnceAWeekWindowFormat, + }, + "reader_endpoint": { + Type: schema.TypeString, + Computed: true, + }, + "replication_source_identifier": { + Type: schema.TypeString, + Optional: true, }, - "restore_to_point_in_time": { Type: schema.TypeList, Optional: true, ForceNew: true, MaxItems: 1, - ConflictsWith: []string{ - "s3_import", - "snapshot_identifier", - }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "restore_to_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: verify.ValidUTCTimestamp, + ConflictsWith: []string{"restore_to_point_in_time.0.use_latest_restorable_time"}, + }, + "restore_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(RestoreType_Values(), false), + }, "source_cluster_identifier": { Type: schema.TypeString, Required: true, @@ -280,43 +307,23 @@ func ResourceCluster() *schema.Resource { validIdentifier, ), }, - - "restore_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - "full-copy", - "copy-on-write", - }, false), - }, - "use_latest_restorable_time": { Type: schema.TypeBool, Optional: true, ForceNew: true, ConflictsWith: []string{"restore_to_point_in_time.0.restore_to_time"}, }, - - "restore_to_time": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: verify.ValidUTCTimestamp, - ConflictsWith: []string{"restore_to_point_in_time.0.use_latest_restorable_time"}, - }, }, }, + ConflictsWith: []string{ + "s3_import", + "snapshot_identifier", + }, }, - "s3_import": { Type: schema.TypeList, Optional: true, MaxItems: 1, - ConflictsWith: []string{ - "snapshot_identifier", - "restore_to_point_in_time", - }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "bucket_name": { @@ -346,160 +353,101 @@ func ResourceCluster() *schema.Resource { }, }, }, + ConflictsWith: []string{ + "snapshot_identifier", + "restore_to_point_in_time", + }, }, - - "final_snapshot_identifier": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { - es = append(es, fmt.Errorf( - "only alphanumeric characters and hyphens allowed in %q", k)) - } - if regexp.MustCompile(`--`).MatchString(value) { - es = append(es, fmt.Errorf("%q cannot contain two consecutive hyphens", k)) - } - if regexp.MustCompile(`-$`).MatchString(value) { - es = append(es, fmt.Errorf("%q cannot end in a hyphen", k)) - } - return + "scaling_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "auto_pause": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "max_capacity": { + Type: schema.TypeInt, + Optional: true, + Default: clusterScalingConfiguration_DefaultMaxCapacity, + }, + "min_capacity": { + Type: schema.TypeInt, + Optional: true, + Default: clusterScalingConfiguration_DefaultMinCapacity, + }, + "seconds_until_auto_pause": { + Type: schema.TypeInt, + Optional: true, + Default: 300, + ValidateFunc: validation.IntBetween(300, 86400), + }, + "timeout_action": { + Type: schema.TypeString, + Optional: true, + Default: TimeoutActionRollbackCapacityChange, + ValidateFunc: validation.StringInSlice(TimeoutAction_Values(), false), + }, + }, + }, + }, + "serverlessv2_scaling_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + DiffSuppressFunc: verify.SuppressMissingOptionalConfigurationBlock, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_capacity": { + Type: schema.TypeFloat, + Required: true, + ValidateFunc: validation.FloatBetween(0.5, 128), + }, + "min_capacity": { + Type: schema.TypeFloat, + Required: true, + ValidateFunc: validation.FloatBetween(0.5, 128), + }, + }, }, }, - "skip_final_snapshot": { Type: schema.TypeBool, Optional: true, Default: false, }, - - "master_username": { - Type: schema.TypeString, - Computed: true, - Optional: true, - ForceNew: true, - }, - - "master_password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - "snapshot_identifier": { Type: schema.TypeString, Optional: true, }, - - "port": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - - // apply_immediately is used to determine when the update modifications - // take place. - // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html - "apply_immediately": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - - "vpc_security_group_ids": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "preferred_backup_window": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: verify.ValidOnceADayWindowFormat, - }, - - "preferred_maintenance_window": { - Type: schema.TypeString, - Optional: true, - Computed: true, - StateFunc: func(val interface{}) string { - if val == nil { - return "" - } - return strings.ToLower(val.(string)) - }, - ValidateFunc: verify.ValidOnceAWeekWindowFormat, - }, - - "backup_retention_period": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - ValidateFunc: validation.IntAtMost(35), - }, - - "kms_key_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ValidateFunc: verify.ValidARN, - }, - - "replication_source_identifier": { + "source_region": { Type: schema.TypeString, Optional: true, + ForceNew: true, }, - - "iam_roles": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "iam_database_authentication_enabled": { + "storage_encrypted": { Type: schema.TypeBool, Optional: true, - }, - - "cluster_resource_id": { - Type: schema.TypeString, Computed: true, + ForceNew: true, }, - - "source_region": { + "storage_type": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - - "enabled_cloudwatch_logs_exports": { + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "vpc_security_group_ids": { Type: schema.TypeSet, Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - "audit", - "error", - "general", - "slowquery", - "postgresql", - }, false), - }, - }, - "enable_http_endpoint": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -611,6 +559,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { requiresModifyDbCluster = true } + if v, ok := d.GetOk("serverlessv2_scaling_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + modifyDbClusterInput.ServerlessV2ScalingConfiguration = expandServerlessV2ScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { opts.VpcSecurityGroupIds = flex.ExpandStringSet(attr) } @@ -831,6 +783,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { modifyDbClusterInput.PreferredMaintenanceWindow = aws.String(val.(string)) case "scaling_configuration": modifyDbClusterInput.ScalingConfiguration = ExpandClusterScalingConfiguration(d.Get("scaling_configuration").([]interface{})) + case "serverlessv2_scaling_configuration": + if len(val.([]interface{})) > 0 && val.([]interface{})[0] != nil { + modifyDbClusterInput.ServerlessV2ScalingConfiguration = expandServerlessV2ScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + } } } } @@ -960,6 +916,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { createOpts.Iops = aws.Int64(int64(attr.(int))) } + if v, ok := d.GetOk("serverlessv2_scaling_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + createOpts.ServerlessV2ScalingConfiguration = expandServerlessV2ScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + if attr, ok := d.GetOkExists("storage_encrypted"); ok { createOpts.StorageEncrypted = aws.Bool(attr.(bool)) } @@ -1041,39 +1001,16 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - input := &rds.DescribeDBClustersInput{ - DBClusterIdentifier: aws.String(d.Id()), - } - - log.Printf("[DEBUG] Describing RDS Cluster: %s", input) - resp, err := conn.DescribeDBClusters(input) + dbc, err := FindDBClusterByID(conn, d.Id()) - if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBClusterNotFoundFault) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] RDS Cluster (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error describing RDS Cluster (%s): %s", d.Id(), err) - } - - if resp == nil { - return fmt.Errorf("Error retrieving RDS cluster: empty response for: %s", input) - } - - var dbc *rds.DBCluster - for _, c := range resp.DBClusters { - if aws.StringValue(c.DBClusterIdentifier) == d.Id() { - dbc = c - break - } - } - - if dbc == nil { - log.Printf("[WARN] RDS Cluster (%s) not found, removing from state", d.Id()) - d.SetId("") - return nil + return fmt.Errorf("error reading RDS Cluster (%s): %w", d.Id(), err) } if err := d.Set("availability_zones", aws.StringValueSlice(dbc.AvailabilityZones)); err != nil { @@ -1146,6 +1083,14 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { d.Set("iops", dbc.Iops) d.Set("storage_encrypted", dbc.StorageEncrypted) + if dbc.ServerlessV2ScalingConfiguration != nil { + if err := d.Set("serverlessv2_scaling_configuration", []interface{}{flattenServerlessV2ScalingConfigurationInfo(dbc.ServerlessV2ScalingConfiguration)}); err != nil { + return fmt.Errorf("error setting serverlessv2_scaling_configuration: %w", err) + } + } else { + d.Set("serverlessv2_scaling_configuration", nil) + } + d.Set("enable_http_endpoint", dbc.HttpEndpointEnabled) var vpcg []string @@ -1313,6 +1258,13 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { requestUpdate = true } + if d.HasChange("serverlessv2_scaling_configuration") { + if v, ok := d.GetOk("serverlessv2_scaling_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + req.ServerlessV2ScalingConfiguration = expandServerlessV2ScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + requestUpdate = true + } + } + if d.HasChange("enable_http_endpoint") { req.EnableHttpEndpoint = aws.Bool(d.Get("enable_http_endpoint").(bool)) requestUpdate = true diff --git a/internal/service/rds/cluster_test.go b/internal/service/rds/cluster_test.go index 96c19ae0a158..4c8308bfca8c 100644 --- a/internal/service/rds/cluster_test.go +++ b/internal/service/rds/cluster_test.go @@ -987,6 +987,7 @@ func TestAccRDSCluster_engineMode(t *testing.T) { testAccCheckClusterExists(resourceName, &dbCluster1), resource.TestCheckResourceAttr(resourceName, "engine_mode", "serverless"), resource.TestCheckResourceAttr(resourceName, "scaling_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.#", "0"), ), }, { @@ -996,6 +997,7 @@ func TestAccRDSCluster_engineMode(t *testing.T) { testAccCheckClusterRecreated(&dbCluster1, &dbCluster2), resource.TestCheckResourceAttr(resourceName, "engine_mode", "provisioned"), resource.TestCheckResourceAttr(resourceName, "scaling_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.#", "0"), ), }, }, @@ -1457,6 +1459,44 @@ func TestAccRDSCluster_scaling(t *testing.T) { }) } +func TestAccRDSCluster_serverlessV2ScalingConfiguration(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var dbCluster rds.DBCluster + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterServerlessV2ScalingConfigurationConfig(rName, 64.0, 0.5), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.0.max_capacity", "64"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.0.min_capacity", "0.5"), + ), + }, + { + Config: testAccClusterServerlessV2ScalingConfigurationConfig(rName, 128.0, 8.5), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.0.max_capacity", "128"), + resource.TestCheckResourceAttr(resourceName, "serverlessv2_scaling_configuration.0.min_capacity", "8.5"), + ), + }, + }, + }) +} + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/11698 func TestAccRDSCluster_Scaling_defaultMinCapacity(t *testing.T) { var dbCluster rds.DBCluster @@ -3797,6 +3837,29 @@ resource "aws_rds_cluster" "test" { `, rName, autoPause, maxCapacity, minCapacity, secondsUntilAutoPause, timeoutAction) } +func testAccClusterServerlessV2ScalingConfigurationConfig(rName string, maxCapacity, minCapacity float64) string { + return fmt.Sprintf(` +data "aws_rds_engine_version" "test" { + engine = "aurora-postgresql" + preferred_versions = ["13.6"] +} + +resource "aws_rds_cluster" "test" { + cluster_identifier = %[1]q + master_password = "barbarbarbar" + master_username = "foo" + skip_final_snapshot = true + engine = data.aws_rds_engine_version.test.engine + engine_version = data.aws_rds_engine_version.test.version + + serverlessv2_scaling_configuration { + max_capacity = %[2]f + min_capacity = %[3]f + } +} +`, rName, maxCapacity, minCapacity) +} + func testAccClusterConfig_ScalingConfiguration_DefaultMinCapacity(rName string, autoPause bool, maxCapacity, secondsUntilAutoPause int, timeoutAction string) string { return fmt.Sprintf(` resource "aws_rds_cluster" "test" { diff --git a/internal/service/rds/consts.go b/internal/service/rds/consts.go index add45745ed46..3f60848c6d68 100644 --- a/internal/service/rds/consts.go +++ b/internal/service/rds/consts.go @@ -50,6 +50,42 @@ const ( EventSubscriptionStatusModifying = "modifying" ) +const ( + EngineAurora = "aurora" + EngineAuroraMySQL = "aurora-mysql" + EngineAuroraPostgreSQL = "aurora-postgresql" + EngineMySQL = "mysql" + EnginePostgres = "postgres" +) + +func Engine_Values() []string { + return []string{ + EngineAurora, + EngineAuroraMySQL, + EngineAuroraPostgreSQL, + EngineMySQL, + EnginePostgres, + } +} + +const ( + EngineModeGlobal = "global" + EngineModeMultiMaster = "multimaster" + EngineModeParallelQuery = "parallelquery" + EngineModeProvisioned = "provisioned" + EngineModeServerless = "serverless" +) + +func EngineMode_Values() []string { + return []string{ + EngineModeGlobal, + EngineModeMultiMaster, + EngineModeParallelQuery, + EngineModeProvisioned, + EngineModeServerless, + } +} + const ( ExportableLogTypeAgent = "agent" ExportableLogTypeAlert = "alert" @@ -64,7 +100,17 @@ const ( ExportableLogTypeUpgrade = "upgrade" ) -func ExportableLogType_Values() []string { +func ClusterExportableLogType_Values() []string { + return []string{ + ExportableLogTypeAudit, + ExportableLogTypeError, + ExportableLogTypeGeneral, + ExportableLogTypePostgreSQL, + ExportableLogTypeSlowQuery, + } +} + +func InstanceExportableLogType_Values() []string { return []string{ ExportableLogTypeAgent, ExportableLogTypeAlert, @@ -79,3 +125,27 @@ func ExportableLogType_Values() []string { ExportableLogTypeUpgrade, } } + +const ( + RestoreTypeCopyOnWrite = "copy-on-write" + RestoreTypeFullCopy = "full-copy" +) + +func RestoreType_Values() []string { + return []string{ + RestoreTypeCopyOnWrite, + RestoreTypeFullCopy, + } +} + +const ( + TimeoutActionForceApplyCapacityChange = "ForceApplyCapacityChange" + TimeoutActionRollbackCapacityChange = "RollbackCapacityChange" +) + +func TimeoutAction_Values() []string { + return []string{ + TimeoutActionForceApplyCapacityChange, + TimeoutActionRollbackCapacityChange, + } +} diff --git a/internal/service/rds/flex.go b/internal/service/rds/flex.go index c8a6175a817a..96ff7efbfc09 100644 --- a/internal/service/rds/flex.go +++ b/internal/service/rds/flex.go @@ -46,6 +46,42 @@ func flattenRDSScalingConfigurationInfo(scalingConfigurationInfo *rds.ScalingCon return []interface{}{m} } +func expandServerlessV2ScalingConfiguration(tfMap map[string]interface{}) *rds.ServerlessV2ScalingConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &rds.ServerlessV2ScalingConfiguration{} + + if v, ok := tfMap["max_capacity"].(float64); ok && v != 0.0 { + apiObject.MaxCapacity = aws.Float64(v) + } + + if v, ok := tfMap["min_capacity"].(float64); ok && v != 0.0 { + apiObject.MinCapacity = aws.Float64(v) + } + + return apiObject +} + +func flattenServerlessV2ScalingConfigurationInfo(apiObject *rds.ServerlessV2ScalingConfigurationInfo) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.MaxCapacity; v != nil { + tfMap["max_capacity"] = aws.Float64Value(v) + } + + if v := apiObject.MinCapacity; v != nil { + tfMap["min_capacity"] = aws.Float64Value(v) + } + + return tfMap +} + func expandOptionConfiguration(configured []interface{}) []*rds.OptionConfiguration { var option []*rds.OptionConfiguration diff --git a/internal/service/rds/instance.go b/internal/service/rds/instance.go index 99ed536cdabd..609eed845c44 100644 --- a/internal/service/rds/instance.go +++ b/internal/service/rds/instance.go @@ -176,7 +176,7 @@ func ResourceInstance() *schema.Resource { Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(ExportableLogType_Values(), false), + ValidateFunc: validation.StringInSlice(InstanceExportableLogType_Values(), false), }, }, "endpoint": { diff --git a/internal/service/rds/validate.go b/internal/service/rds/validate.go index 6e3da6ad67ea..bb597866e3b1 100644 --- a/internal/service/rds/validate.go +++ b/internal/service/rds/validate.go @@ -144,13 +144,7 @@ func validSubnetGroupNamePrefix(v interface{}, k string) (ws []string, errors [] } func validEngine() schema.SchemaValidateFunc { - return validation.StringInSlice([]string{ - "aurora", - "aurora-mysql", - "aurora-postgresql", - "postgres", - "mysql", - }, false) + return validation.StringInSlice(Engine_Values(), false) } func validIdentifier(v interface{}, k string) (ws []string, errors []error) { diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index 527144a51a93..20b86cf795b2 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -158,6 +158,7 @@ The following arguments are supported: * `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. * `restore_to_point_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/USER_PIT.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. More details below. * `skip_final_snapshot` - (Optional) Determines whether a final DB snapshot is created before the DB cluster is deleted. If true is specified, no DB snapshot is created. If false is specified, a DB snapshot is created before the DB cluster is deleted, using the value from `final_snapshot_identifier`. Default is `false`. * `snapshot_identifier` - (Optional) Specifies whether or not to create this cluster from a snapshot. You can use either the name or ARN when specifying a DB cluster snapshot, or the ARN when specifying a DB snapshot. * `source_region` - (Optional) The source region for an encrypted replica DB cluster. @@ -248,6 +249,24 @@ resource "aws_rds_cluster" "example" { * `seconds_until_auto_pause` - (Optional) The time, in seconds, before an Aurora DB cluster in serverless mode is paused. Valid values are `300` through `86400`. Defaults to `300`. * `timeout_action` - (Optional) The action to take when the timeout is reached. Valid values: `ForceApplyCapacityChange`, `RollbackCapacityChange`. Defaults to `RollbackCapacityChange`. See [documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless.how-it-works.html#aurora-serverless.how-it-works.timeout-action). +### serverlessv2_scaling_configuration Argument Reference + +Example: + +```terraform +resource "aws_rds_cluster" "example" { + # ... other configuration ... + + serverlessv2_scaling_configuration { + max_capacity = 128.0 + min_capacity = 0.5 + } +} +``` + +* `max_capacity` - (Required) The maximum capacity for an Aurora DB cluster in `serverlessv2` DB engine mode. The maximum capacity must be greater than or equal to the minimum capacity. Valid capacity values are in a range of `0.5` up to `128` in steps of `0.5`. +* `min_capacity` - (Required) The minimum capacity for an Aurora DB cluster in `serverlessv2` DB engine mode. The minimum capacity must be lesser than or equal to the maximum capacity. Valid capacity values are in a range of `0.5` up to `128` in steps of `0.5`. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: