From d84e64bdf6b48d814f22a230cc2108ee3656bfcf Mon Sep 17 00:00:00 2001 From: Gufran Date: Fri, 4 Jan 2019 13:52:20 +0530 Subject: [PATCH 01/14] Support Aurora point-in-time restore --- aws/resource_aws_rds_cluster.go | 186 ++++++++++++++++++++++- aws/resource_aws_rds_cluster_test.go | 74 +++++++++ aws/validators.go | 9 ++ aws/validators_test.go | 26 ++++ website/docs/r/rds_cluster.html.markdown | 26 +++- 5 files changed, 319 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 4590a3fc09b..004f36f9431 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -217,12 +217,58 @@ func resourceAwsRDSCluster() *schema.Resource { ForceNew: true, }, + "restore_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{ + "source_cluster_identifier": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRdsIdentifier, + }, + + "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_in_time.restore_to_time"}, + }, + + "restore_to_time": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateRdsRestorableTimeFormat, + }, + }, + }, + }, + "s3_import": { Type: schema.TypeList, Optional: true, MaxItems: 1, ConflictsWith: []string{ "snapshot_identifier", + "restore_in_time", }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -405,7 +451,7 @@ func resourceAwsRDSCluster() *schema.Resource { Default: false, }, - "tags": tagsSchema(), + "tags": tagsSchemaComputed(), }, } } @@ -650,6 +696,126 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return err } + } else if v, ok := d.GetOk("restore_in_time"); ok { + pointInTime := v.([]interface{})[0].(map[string]interface{}) + + restoreToTimeAttr := pointInTime["restore_to_time"].(string) + useLatestRestorableTimeAttr := pointInTime["use_latest_restorable_time"].(bool) + + if useLatestRestorableTimeAttr && restoreToTimeAttr != "" { + return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: Either "restore_to_time" or "use_latest_restorable_time" must be set`, d.Get("database_name").(string)) + } + + createOpts := &rds.RestoreDBClusterToPointInTimeInput{ + DBClusterIdentifier: aws.String(identifier), + DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), + SourceDBClusterIdentifier: aws.String(pointInTime["source_cluster_identifier"].(string)), + Tags: tags, + } + + if attr, ok := pointInTime["restore_type"]; ok { + createOpts.RestoreType = aws.String(attr.(string)) + } + + if useLatestRestorableTimeAttr { + createOpts.UseLatestRestorableTime = aws.Bool(useLatestRestorableTimeAttr) + } else if restoreToTimeAttr != "" { + restoreToTime, _ := time.Parse(time.RFC3339, restoreToTimeAttr) + createOpts.RestoreToTime = aws.Time(restoreToTime) + } else { + return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: Both "restore_to_time" and "use_latest_restorable_time" cannot be set`, d.Get("database_name").(string)) + } + + if err := validateRestoreToPointInTimeInput(d); err != nil { + return err + } + + if v, ok := d.GetOk("backtrack_window"); ok && v.(int) > 0 { + createOpts.BacktrackWindow = aws.Int64(int64(v.(int))) + } + + if attr, ok := d.GetOk("db_subnet_group_name"); ok { + createOpts.DBSubnetGroupName = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("port"); ok { + createOpts.Port = aws.Int64(int64(attr.(int))) + } + + if attr, ok := d.GetOk("option_group_name"); ok { + createOpts.OptionGroupName = aws.String(attr.(string)) + } + + if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { + createOpts.VpcSecurityGroupIds = expandStringList(attr.List()) + } + + if attr, ok := d.GetOk("kms_key_id"); ok { + createOpts.KmsKeyId = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("enabled_cloudwatch_logs_exports"); ok && len(attr.([]interface{})) > 0 { + createOpts.EnableCloudwatchLogsExports = expandStringList(attr.([]interface{})) + } + + if attr, ok := d.GetOk("iam_database_authentication_enabled"); ok { + createOpts.EnableIAMDatabaseAuthentication = aws.Bool(attr.(bool)) + } + + if attr, ok := d.GetOk("db_cluster_parameter_group_name"); ok { + createOpts.DBClusterParameterGroupName = aws.String(attr.(string)) + } + + requireUpdateAttrs := []string{ + "master_password", + "final_snapshot_identifier", + "backup_retention_period", + "preferred_backup_window", + "preferred_maintenance_window", + "scaling_configuration", + } + + for _, attr := range requireUpdateAttrs { + if val, ok := d.GetOk(attr); ok { + requiresModifyDbCluster = true + switch attr { + case "master_password": + modifyDbClusterInput.MasterUserPassword = aws.String(val.(string)) + case "backup_retention_period": + modifyDbClusterInput.BackupRetentionPeriod = aws.Int64(int64(val.(int))) + case "preferred_backup_window": + modifyDbClusterInput.PreferredBackupWindow = aws.String(val.(string)) + case "preferred_maintenance_window": + modifyDbClusterInput.PreferredMaintenanceWindow = aws.String(val.(string)) + case "scaling_configuration": + modifyDbClusterInput.ScalingConfiguration = expandRdsScalingConfiguration(d.Get("scaling_configuration").([]interface{})) + } + } + } + + log.Printf("[DEBUG] RDS Cluster restore options: %s", createOpts) + + err := resource.Retry(5*time.Minute, func() *resource.RetryError { + resp, err := conn.RestoreDBClusterToPointInTime(createOpts) + if err != nil { + if isAWSErr(err, "DBClusterSnapshotNotFoundFault", "DBClusterSnapshotIdentifier doesn't refer to an existing DB cluster snapshot") { + return resource.RetryableError(err) + } + if isAWSErr(err, "KMSKeyNotAccessibleFault", "An error occurred accessing an AWS KMS key") { + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) + return nil + }) + + if err != nil { + log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) + return err + } } else { createOpts := &rds.CreateDBClusterInput{ @@ -823,6 +989,24 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return resourceAwsRDSClusterRead(d, meta) } +func validateRestoreToPointInTimeInput(d *schema.ResourceData) error { + invalidAttrs := []string{ + "database_name", + "master_username", + "storage_encrypted", + "replication_source_identifier", + "source_region", + } + + for _, attr := range invalidAttrs { + if _, ok := d.GetOk(attr); ok { + return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: %q attribute must not be set for point in time restore operation`, d.Get("database_name"), attr) + } + } + + return nil +} + func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).rdsconn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index fea19d50336..a5fa06c2caf 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -364,6 +364,32 @@ func TestAccAWSRDSCluster_s3Restore(t *testing.T) { }) } +func TestAccAWSRDSCluster_PointInTimeRestore(t *testing.T) { + var v rds.DBCluster + var c rds.DBCluster + + parentId := acctest.RandomWithPrefix("tf-acc-point-in-time-restore-seed-test") + restoredId := acctest.RandomWithPrefix("tf-acc-point-in-time-restored-test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, restoredId), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSClusterExists("aws_rds_cluster.test", &v), + resource.TestCheckResourceAttr("aws_rds_cluster.test", "engine", "aurora-mysql"), + testAccCheckAWSClusterExists("aws_rds_cluster.restored_pit", &c), + resource.TestCheckResourceAttr("aws_rds_cluster.restored_pit", "cluster_identifier", restoredId), + resource.TestCheckResourceAttr("aws_rds_cluster.test", "engine", "aurora-mysql"), + ), + }, + }, + }) +} + func TestAccAWSRDSCluster_generatedName(t *testing.T) { var v rds.DBCluster resourceName := "aws_rds_cluster.test" @@ -2634,6 +2660,54 @@ resource "aws_rds_cluster" "default" { `, n) } +func testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, childId string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +resource "aws_rds_cluster" "test" { + cluster_identifier = "%s" + master_username = "root" + master_password = "password" + db_subnet_group_name = "${aws_db_subnet_group.test.name}" + skip_final_snapshot = true + engine = "aurora-mysql" +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + tags = { + Name = "%s-vpc" + } +} + +resource "aws_subnet" "subnets" { + count = "${length(data.aws_availability_zones.available.names)}" + vpc_id = "${aws_vpc.test.id}" + cidr_block = "10.0.${count.index}.0/24" + availability_zone = "${data.aws_availability_zones.available.names[count.index]}" + tags = { + Name = "%s-subnet-${count.index}" + } +} + +resource "aws_db_subnet_group" "test" { + name = "%s-db-subnet-group" + subnet_ids = ["${aws_subnet.subnets.*.id}"] +} + +resource "aws_rds_cluster" "restored_pit" { + cluster_identifier = "%s" + skip_final_snapshot = true + engine = "aurora-mysql" + restore_in_time { + source_cluster_identifier = "${aws_rds_cluster.test.cluster_identifier}" + restore_type = "full-copy" + use_latest_restorable_time = "true" + } +} +`, parentId, parentId, parentId, parentId, childId) +} + func testAccAWSClusterConfigTags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_rds_cluster" "test" { diff --git a/aws/validators.go b/aws/validators.go index 70419f4d727..9df062cee1b 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -1081,6 +1081,15 @@ func validateOnceADayWindowFormat(v interface{}, k string) (ws []string, errors return } +func validateRdsRestorableTimeFormat(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + _, err := time.Parse(time.RFC3339, value) + if err != nil { + errors = append(errors, fmt.Errorf("%q must be in RFC3339 time format %q. Example: %s", k, time.RFC3339, err)) + } + return +} + // Validates that ECS Placement Constraints are set correctly // Takes type, and expression as strings func validateAwsEcsPlacementConstraint(constType, constExpr string) error { diff --git a/aws/validators_test.go b/aws/validators_test.go index 70977da5ddd..826b36c9c85 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -3220,3 +3220,29 @@ func TestValidateServiceDiscoveryNamespaceName(t *testing.T) { } } } + +func TestValidateRdsRestorableTimeFormat(t *testing.T) { + validT := []string{ + "2006-01-02T15:04:05Z", + } + + invalidT := []string{ + "2015-03-07 23:45:00", + "27-03-2019 23:45:00", + "Mon, 02 Jan 2006 15:04:05 -0700", + } + + for _, f := range validT { + _, errors := validateRdsRestorableTimeFormat(f, "valid_restorable_time_format") + if len(errors) > 0 { + t.Fatalf("Expected the time %q to be in valid format, got error %q", f, errors) + } + } + + for _, f := range invalidT { + _, errors := validateRdsRestorableTimeFormat(f, "invalid_restorable_time_format") + if len(errors) == 0 { + t.Fatalf("Expected the time %q to fail validation", f) + } + } +} \ No newline at end of file diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index 91da320cd74..c72bdf9e832 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -125,6 +125,7 @@ The following arguments are supported: * `preferred_backup_window` - (Optional) The 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) The 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](/docs/configuration/resources.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. +* `restore_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/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. * `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. @@ -133,7 +134,6 @@ The following arguments are supported: * `tags` - (Optional) A map of tags to assign to the DB cluster. * `vpc_security_group_ids` - (Optional) List of VPC security groups to associate with the Cluster - ### S3 Import Options Full details on the core parameters and impacts are in the API Docs: [RestoreDBClusterFromS3](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_RestoreDBClusterFromS3.html). Requires that the S3 bucket be in the same region as the RDS cluster you're trying to create. Sample: @@ -190,6 +190,30 @@ 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). +### restore_in_time Argument Reference + +Example: + +```hcl +resource "aws_rds_cluster" "example-clone" { + # ... other configuration ... + + restore_in_time { + source_cluster_identifier = "example" + restore_type = "copy-on-write" + use_latest_restorable_time = true + } +} +``` + +* `source_cluster_identifier` - (Required) The identifier of the source database cluster from which to restore. +* `restore_type` - (Optional) Type of restore to be performed. + Valid options are `full-copy` (default) and `copy-on-write`. +* `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. +* `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. + +~> **NOTE:** `use_latest_restorable_time` and `restore_to_time` conflict with each other. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 0e9be908729c374258f78d079879a7404b70b7a7 Mon Sep 17 00:00:00 2001 From: Gufran Date: Fri, 6 Nov 2020 23:43:00 +0530 Subject: [PATCH 02/14] Rebase on master and address reviews --- aws/resource_aws_rds_cluster.go | 25 +++++++++++------------- aws/resource_aws_rds_cluster_test.go | 16 +++++++-------- aws/validators_test.go | 2 +- website/docs/r/rds_cluster.html.markdown | 6 +++--- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 004f36f9431..31c129d09c7 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -3,6 +3,7 @@ package aws import ( "errors" "fmt" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" "log" "regexp" "strings" @@ -217,7 +218,7 @@ func resourceAwsRDSCluster() *schema.Resource { ForceNew: true, }, - "restore_in_time": { + "restore_to_point_in_time": { Type: schema.TypeList, Optional: true, ForceNew: true, @@ -249,7 +250,7 @@ func resourceAwsRDSCluster() *schema.Resource { Type: schema.TypeBool, Optional: true, ForceNew: true, - ConflictsWith: []string{"restore_in_time.restore_to_time"}, + ConflictsWith: []string{"restore_to_point_in_time.restore_to_time"}, }, "restore_to_time": { @@ -268,7 +269,7 @@ func resourceAwsRDSCluster() *schema.Resource { MaxItems: 1, ConflictsWith: []string{ "snapshot_identifier", - "restore_in_time", + "restore_to_point_in_time", }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -451,7 +452,7 @@ func resourceAwsRDSCluster() *schema.Resource { Default: false, }, - "tags": tagsSchemaComputed(), + "tags": tagsSchema(), }, } } @@ -696,7 +697,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return err } - } else if v, ok := d.GetOk("restore_in_time"); ok { + } else if v, ok := d.GetOk("restore_to_point_in_time"); ok { pointInTime := v.([]interface{})[0].(map[string]interface{}) restoreToTimeAttr := pointInTime["restore_to_time"].(string) @@ -768,7 +769,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error requireUpdateAttrs := []string{ "master_password", - "final_snapshot_identifier", "backup_retention_period", "preferred_backup_window", "preferred_maintenance_window", @@ -788,7 +788,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error case "preferred_maintenance_window": modifyDbClusterInput.PreferredMaintenanceWindow = aws.String(val.(string)) case "scaling_configuration": - modifyDbClusterInput.ScalingConfiguration = expandRdsScalingConfiguration(d.Get("scaling_configuration").([]interface{})) + modifyDbClusterInput.ScalingConfiguration = expandRdsClusterScalingConfiguration(d.Get("scaling_configuration").([]interface{})) } } } @@ -798,13 +798,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error err := resource.Retry(5*time.Minute, func() *resource.RetryError { resp, err := conn.RestoreDBClusterToPointInTime(createOpts) if err != nil { - if isAWSErr(err, "DBClusterSnapshotNotFoundFault", "DBClusterSnapshotIdentifier doesn't refer to an existing DB cluster snapshot") { - return resource.RetryableError(err) - } - if isAWSErr(err, "KMSKeyNotAccessibleFault", "An error occurred accessing an AWS KMS key") { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) } @@ -812,6 +805,10 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return nil }) + if tfresource.TimedOut(err) { + _, err = conn.RestoreDBClusterToPointInTime(createOpts) + } + if err != nil { log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) return err diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index a5fa06c2caf..8df2abf3fcd 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -2668,7 +2668,7 @@ resource "aws_rds_cluster" "test" { cluster_identifier = "%s" master_username = "root" master_password = "password" - db_subnet_group_name = "${aws_db_subnet_group.test.name}" + db_subnet_group_name = aws_db_subnet_group.test.name skip_final_snapshot = true engine = "aurora-mysql" } @@ -2681,10 +2681,10 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "subnets" { - count = "${length(data.aws_availability_zones.available.names)}" - vpc_id = "${aws_vpc.test.id}" + count = length(data.aws_availability_zones.available.names) + vpc_id = aws_vpc.test.id cidr_block = "10.0.${count.index}.0/24" - availability_zone = "${data.aws_availability_zones.available.names[count.index]}" + availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = "%s-subnet-${count.index}" } @@ -2692,17 +2692,17 @@ resource "aws_subnet" "subnets" { resource "aws_db_subnet_group" "test" { name = "%s-db-subnet-group" - subnet_ids = ["${aws_subnet.subnets.*.id}"] + subnet_ids = aws_subnet.subnets[*].id } resource "aws_rds_cluster" "restored_pit" { cluster_identifier = "%s" skip_final_snapshot = true engine = "aurora-mysql" - restore_in_time { - source_cluster_identifier = "${aws_rds_cluster.test.cluster_identifier}" + restore_to_point_in_time { + source_cluster_identifier = aws_rds_cluster.test.cluster_identifier restore_type = "full-copy" - use_latest_restorable_time = "true" + use_latest_restorable_time = true } } `, parentId, parentId, parentId, parentId, childId) diff --git a/aws/validators_test.go b/aws/validators_test.go index 826b36c9c85..62a41bc98a1 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -3245,4 +3245,4 @@ func TestValidateRdsRestorableTimeFormat(t *testing.T) { t.Fatalf("Expected the time %q to fail validation", f) } } -} \ No newline at end of file +} diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index c72bdf9e832..cae0915c96e 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -125,7 +125,7 @@ The following arguments are supported: * `preferred_backup_window` - (Optional) The 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) The 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](/docs/configuration/resources.html#ignore_changes) to prevent Terraform from showing differences for this argument instead of configuring this value. -* `restore_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PIT.html). More details below. +* `restore_to_point_in_time` - (Optional) Nested attribute for [point in time restore](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/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. * `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. @@ -190,7 +190,7 @@ 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). -### restore_in_time Argument Reference +### restore_to_point_in_time Argument Reference Example: @@ -198,7 +198,7 @@ Example: resource "aws_rds_cluster" "example-clone" { # ... other configuration ... - restore_in_time { + restore_To_point_in_time { source_cluster_identifier = "example" restore_type = "copy-on-write" use_latest_restorable_time = true From 06ec24b8c1e9e0cf6c59848cbb4d35735ed242fe Mon Sep 17 00:00:00 2001 From: Gufran Date: Sat, 7 Nov 2020 00:15:09 +0530 Subject: [PATCH 03/14] Fix lint issues --- aws/resource_aws_rds_cluster.go | 17 +++++------- aws/resource_aws_rds_cluster_test.go | 34 ++++++++++++------------ website/docs/r/rds_cluster.html.markdown | 6 ++--- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 31c129d09c7..e25259bdc7a 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -3,7 +3,6 @@ package aws import ( "errors" "fmt" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" "log" "regexp" "strings" @@ -16,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) const ( @@ -250,7 +250,7 @@ func resourceAwsRDSCluster() *schema.Resource { Type: schema.TypeBool, Optional: true, ForceNew: true, - ConflictsWith: []string{"restore_to_point_in_time.restore_to_time"}, + ConflictsWith: []string{"restore_to_point_in_time.0.restore_to_time"}, }, "restore_to_time": { @@ -731,7 +731,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return err } - if v, ok := d.GetOk("backtrack_window"); ok && v.(int) > 0 { + if v, ok := d.GetOk("backtrack_window"); ok { createOpts.BacktrackWindow = aws.Int64(int64(v.(int))) } @@ -795,15 +795,10 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[DEBUG] RDS Cluster restore options: %s", createOpts) - err := resource.Retry(5*time.Minute, func() *resource.RetryError { - resp, err := conn.RestoreDBClusterToPointInTime(createOpts) - if err != nil { - return resource.NonRetryableError(err) - } - + resp, err := conn.RestoreDBClusterToPointInTime(createOpts) + if err != nil { log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) - return nil - }) + } if tfresource.TimedOut(err) { _, err = conn.RestoreDBClusterToPointInTime(createOpts) diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index 8df2abf3fcd..b5adab35f33 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -2665,12 +2665,12 @@ func testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, childId string) data "aws_availability_zones" "available" {} resource "aws_rds_cluster" "test" { - cluster_identifier = "%s" - master_username = "root" - master_password = "password" + cluster_identifier = "%s" + master_username = "root" + master_password = "password" db_subnet_group_name = aws_db_subnet_group.test.name - skip_final_snapshot = true - engine = "aurora-mysql" + skip_final_snapshot = true + engine = "aurora-mysql" } resource "aws_vpc" "test" { @@ -2681,9 +2681,9 @@ resource "aws_vpc" "test" { } resource "aws_subnet" "subnets" { - count = length(data.aws_availability_zones.available.names) - vpc_id = aws_vpc.test.id - cidr_block = "10.0.${count.index}.0/24" + count = length(data.aws_availability_zones.available.names) + vpc_id = aws_vpc.test.id + cidr_block = "10.0.${count.index}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index] tags = { Name = "%s-subnet-${count.index}" @@ -2691,19 +2691,19 @@ resource "aws_subnet" "subnets" { } resource "aws_db_subnet_group" "test" { - name = "%s-db-subnet-group" + name = "%s-db-subnet-group" subnet_ids = aws_subnet.subnets[*].id } resource "aws_rds_cluster" "restored_pit" { - cluster_identifier = "%s" - skip_final_snapshot = true - engine = "aurora-mysql" - restore_to_point_in_time { - source_cluster_identifier = aws_rds_cluster.test.cluster_identifier - restore_type = "full-copy" - use_latest_restorable_time = true - } + cluster_identifier = "%s" + skip_final_snapshot = true + engine = "aurora-mysql" + restore_to_point_in_time { + source_cluster_identifier = aws_rds_cluster.test.cluster_identifier + restore_type = "full-copy" + use_latest_restorable_time = true + } } `, parentId, parentId, parentId, parentId, childId) } diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index cae0915c96e..2c56a3e2f54 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -199,8 +199,8 @@ resource "aws_rds_cluster" "example-clone" { # ... other configuration ... restore_To_point_in_time { - source_cluster_identifier = "example" - restore_type = "copy-on-write" + source_cluster_identifier = "example" + restore_type = "copy-on-write" use_latest_restorable_time = true } } @@ -212,7 +212,7 @@ resource "aws_rds_cluster" "example-clone" { * `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. * `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. -~> **NOTE:** `use_latest_restorable_time` and `restore_to_time` conflict with each other. +~> **NOTE:** `use_latest_restorable_time` and `restore_to_time` conflict with each other. ## Attributes Reference From e76dac9190c79d8f9fc3e6a89a7f6cd7c8405a01 Mon Sep 17 00:00:00 2001 From: Gufran Date: Sat, 7 Nov 2020 00:56:56 +0530 Subject: [PATCH 04/14] Rename date validator function --- aws/resource_aws_rds_cluster.go | 2 +- aws/validators.go | 2 +- aws/validators_test.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index e25259bdc7a..cd84652eb66 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -257,7 +257,7 @@ func resourceAwsRDSCluster() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - ValidateFunc: validateRdsRestorableTimeFormat, + ValidateFunc: validateUTCTimestamp, }, }, }, diff --git a/aws/validators.go b/aws/validators.go index 9df062cee1b..50cdec3aae1 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -1081,7 +1081,7 @@ func validateOnceADayWindowFormat(v interface{}, k string) (ws []string, errors return } -func validateRdsRestorableTimeFormat(v interface{}, k string) (ws []string, errors []error) { +func validateUTCTimestamp(v interface{}, k string) (ws []string, errors []error) { value := v.(string) _, err := time.Parse(time.RFC3339, value) if err != nil { diff --git a/aws/validators_test.go b/aws/validators_test.go index 62a41bc98a1..1be9391326f 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -3221,7 +3221,7 @@ func TestValidateServiceDiscoveryNamespaceName(t *testing.T) { } } -func TestValidateRdsRestorableTimeFormat(t *testing.T) { +func TestValidateUTCTimestamp(t *testing.T) { validT := []string{ "2006-01-02T15:04:05Z", } @@ -3233,14 +3233,14 @@ func TestValidateRdsRestorableTimeFormat(t *testing.T) { } for _, f := range validT { - _, errors := validateRdsRestorableTimeFormat(f, "valid_restorable_time_format") + _, errors := validateUTCTimestamp(f, "valid_restorable_time_format") if len(errors) > 0 { t.Fatalf("Expected the time %q to be in valid format, got error %q", f, errors) } } for _, f := range invalidT { - _, errors := validateRdsRestorableTimeFormat(f, "invalid_restorable_time_format") + _, errors := validateUTCTimestamp(f, "invalid_restorable_time_format") if len(errors) == 0 { t.Fatalf("Expected the time %q to fail validation", f) } From ae27e5dbb22f79580360a40bd61ca255375d8514 Mon Sep 17 00:00:00 2001 From: Mohammad Gufran Date: Wed, 11 Nov 2020 15:24:05 +0530 Subject: [PATCH 05/14] Update aws/resource_aws_rds_cluster_test.go Co-authored-by: angie pinilla --- aws/resource_aws_rds_cluster_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index b5adab35f33..cb386405609 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -2665,7 +2665,7 @@ func testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, childId string) data "aws_availability_zones" "available" {} resource "aws_rds_cluster" "test" { - cluster_identifier = "%s" + cluster_identifier = "%[1]s" master_username = "root" master_password = "password" db_subnet_group_name = aws_db_subnet_group.test.name @@ -2676,7 +2676,7 @@ resource "aws_rds_cluster" "test" { resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" tags = { - Name = "%s-vpc" + Name = "%[1]s-vpc" } } @@ -2686,12 +2686,12 @@ resource "aws_subnet" "subnets" { cidr_block = "10.0.${count.index}.0/24" availability_zone = data.aws_availability_zones.available.names[count.index] tags = { - Name = "%s-subnet-${count.index}" + Name = "%[1]s-subnet-${count.index}" } } resource "aws_db_subnet_group" "test" { - name = "%s-db-subnet-group" + name = "%[1]s-db-subnet-group" subnet_ids = aws_subnet.subnets[*].id } @@ -2705,7 +2705,7 @@ resource "aws_rds_cluster" "restored_pit" { use_latest_restorable_time = true } } -`, parentId, parentId, parentId, parentId, childId) +`, parentId, childId) } func testAccAWSClusterConfigTags1(rName, tagKey1, tagValue1 string) string { From fd01461660ee8f6a5586834224b6a7e1b89a183f Mon Sep 17 00:00:00 2001 From: Mohammad Gufran Date: Wed, 11 Nov 2020 15:25:48 +0530 Subject: [PATCH 06/14] Update aws/resource_aws_rds_cluster.go Co-authored-by: angie pinilla --- aws/resource_aws_rds_cluster.go | 1 + 1 file changed, 1 insertion(+) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index cd84652eb66..cf57e8722ee 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -258,6 +258,7 @@ func resourceAwsRDSCluster() *schema.Resource { Optional: true, ForceNew: true, ValidateFunc: validateUTCTimestamp, + ConflictsWith: []string{"restore_to_point_in_time.0.use_latest_restorable_time"}, }, }, }, From 95cdba80d392c90a35fb59c09e983050666f5105 Mon Sep 17 00:00:00 2001 From: Gufran Date: Wed, 11 Nov 2020 15:57:24 +0530 Subject: [PATCH 07/14] Address the reviews --- aws/resource_aws_rds_cluster.go | 43 +++++++++------------- aws/resource_aws_rds_cluster_test.go | 3 +- website/docs/r/rds_cluster.html.markdown | 46 ++++++++++++------------ 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index cf57e8722ee..2aa70adb14a 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" - "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" ) const ( @@ -254,10 +253,10 @@ func resourceAwsRDSCluster() *schema.Resource { }, "restore_to_time": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validateUTCTimestamp, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateUTCTimestamp, ConflictsWith: []string{"restore_to_point_in_time.0.use_latest_restorable_time"}, }, }, @@ -700,14 +699,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error } else if v, ok := d.GetOk("restore_to_point_in_time"); ok { pointInTime := v.([]interface{})[0].(map[string]interface{}) - - restoreToTimeAttr := pointInTime["restore_to_time"].(string) - useLatestRestorableTimeAttr := pointInTime["use_latest_restorable_time"].(bool) - - if useLatestRestorableTimeAttr && restoreToTimeAttr != "" { - return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: Either "restore_to_time" or "use_latest_restorable_time" must be set`, d.Get("database_name").(string)) - } - createOpts := &rds.RestoreDBClusterToPointInTimeInput{ DBClusterIdentifier: aws.String(identifier), DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)), @@ -715,17 +706,21 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error Tags: tags, } - if attr, ok := pointInTime["restore_type"]; ok { - createOpts.RestoreType = aws.String(attr.(string)) + if v, ok := pointInTime["restore_in_time"].(string); ok { + restoreToTime, _ := time.Parse(time.RFC3339, v) + createOpts.RestoreToTime = aws.Time(restoreToTime) } - if useLatestRestorableTimeAttr { - createOpts.UseLatestRestorableTime = aws.Bool(useLatestRestorableTimeAttr) - } else if restoreToTimeAttr != "" { - restoreToTime, _ := time.Parse(time.RFC3339, restoreToTimeAttr) - createOpts.RestoreToTime = aws.Time(restoreToTime) - } else { - return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: Both "restore_to_time" and "use_latest_restorable_time" cannot be set`, d.Get("database_name").(string)) + if v, ok := pointInTime["use_latest_restorable_time"].(bool); ok && v { + createOpts.UseLatestRestorableTime = aws.Bool(v) + } + + if createOpts.RestoreToTime == nil && createOpts.UseLatestRestorableTime == nil { + return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: Either "restore_to_time" or "use_latest_restorable_time" must be set`, d.Get("database_name").(string)) + } + + if attr, ok := pointInTime["restore_type"].(string); ok { + createOpts.RestoreType = aws.String(attr) } if err := validateRestoreToPointInTimeInput(d); err != nil { @@ -801,10 +796,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) } - if tfresource.TimedOut(err) { - _, err = conn.RestoreDBClusterToPointInTime(createOpts) - } - if err != nil { log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) return err diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index cb386405609..2ad404a1eb0 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -380,10 +380,9 @@ func TestAccAWSRDSCluster_PointInTimeRestore(t *testing.T) { Config: testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, restoredId), Check: resource.ComposeTestCheckFunc( testAccCheckAWSClusterExists("aws_rds_cluster.test", &v), - resource.TestCheckResourceAttr("aws_rds_cluster.test", "engine", "aurora-mysql"), testAccCheckAWSClusterExists("aws_rds_cluster.restored_pit", &c), resource.TestCheckResourceAttr("aws_rds_cluster.restored_pit", "cluster_identifier", restoredId), - resource.TestCheckResourceAttr("aws_rds_cluster.test", "engine", "aurora-mysql"), + resource.TestCheckResourceAttrPair("aws_rds_cluster.restored_pit", "engine", "aws_rds_cluster.test", "engine"), ), }, }, diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index 2c56a3e2f54..f83fafd3a54 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -162,6 +162,28 @@ resource "aws_rds_cluster" "db" { This will not recreate the resource if the S3 object changes in some way. It's only used to initialize the database. This only works currently with the aurora engine. See AWS for currently supported engines and options. See [Aurora S3 Migration Docs](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/AuroraMySQL.Migrating.ExtMySQL.html#AuroraMySQL.Migrating.ExtMySQL.S3). +### restore_to_point_in_time Argument Reference + +Example: + +```hcl +resource "aws_rds_cluster" "example-clone" { + # ... other configuration ... + + restore_to_point_in_time { + source_cluster_identifier = "example" + restore_type = "copy-on-write" + use_latest_restorable_time = true + } +} +``` + +* `source_cluster_identifier` - (Required) The identifier of the source database cluster from which to restore. +* `restore_type` - (Optional) Type of restore to be performed. + Valid options are `full-copy` (default) and `copy-on-write`. +* `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. +* `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. + ### scaling_configuration Argument Reference ~> **NOTE:** `scaling_configuration` configuration is only valid when `engine_mode` is set to `serverless`. @@ -190,30 +212,6 @@ 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). -### restore_to_point_in_time Argument Reference - -Example: - -```hcl -resource "aws_rds_cluster" "example-clone" { - # ... other configuration ... - - restore_To_point_in_time { - source_cluster_identifier = "example" - restore_type = "copy-on-write" - use_latest_restorable_time = true - } -} -``` - -* `source_cluster_identifier` - (Required) The identifier of the source database cluster from which to restore. -* `restore_type` - (Optional) Type of restore to be performed. - Valid options are `full-copy` (default) and `copy-on-write`. -* `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. -* `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. - -~> **NOTE:** `use_latest_restorable_time` and `restore_to_time` conflict with each other. - ## Attributes Reference In addition to all arguments above, the following attributes are exported: From d4f14c7e9c127d01fdfd9973d70ea065c0796655 Mon Sep 17 00:00:00 2001 From: Gufran Date: Wed, 11 Nov 2020 16:04:27 +0530 Subject: [PATCH 08/14] Use testAccAvailableAZsNoOptInConfig to find AZ --- aws/resource_aws_rds_cluster_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index 2ad404a1eb0..888c03a0ccd 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -2660,9 +2660,7 @@ resource "aws_rds_cluster" "default" { } func testAccAWSClusterConfig_pointInTimeRestoreSource(parentId, childId string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" {} - + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` resource "aws_rds_cluster" "test" { cluster_identifier = "%[1]s" master_username = "root" @@ -2704,7 +2702,7 @@ resource "aws_rds_cluster" "restored_pit" { use_latest_restorable_time = true } } -`, parentId, childId) +`, parentId, childId)) } func testAccAWSClusterConfigTags1(rName, tagKey1, tagValue1 string) string { From e1b736abff1d04f393b9fec4631cd69ad9cab635 Mon Sep 17 00:00:00 2001 From: Gufran Date: Wed, 11 Nov 2020 16:07:27 +0530 Subject: [PATCH 09/14] Cleanup test config --- aws/resource_aws_rds_cluster_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_rds_cluster_test.go b/aws/resource_aws_rds_cluster_test.go index 888c03a0ccd..d8db86318fe 100644 --- a/aws/resource_aws_rds_cluster_test.go +++ b/aws/resource_aws_rds_cluster_test.go @@ -2695,7 +2695,7 @@ resource "aws_db_subnet_group" "test" { resource "aws_rds_cluster" "restored_pit" { cluster_identifier = "%s" skip_final_snapshot = true - engine = "aurora-mysql" + engine = aws_rds_cluster.test.engine restore_to_point_in_time { source_cluster_identifier = aws_rds_cluster.test.cluster_identifier restore_type = "full-copy" From 2d3f60676ab22a0b1a7f5867738eb59bc80103cc Mon Sep 17 00:00:00 2001 From: Mohammad Gufran Date: Wed, 11 Nov 2020 20:04:45 +0530 Subject: [PATCH 10/14] Update aws/resource_aws_rds_cluster.go Co-authored-by: angie pinilla --- aws/resource_aws_rds_cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 2aa70adb14a..418ba8f3192 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -706,7 +706,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error Tags: tags, } - if v, ok := pointInTime["restore_in_time"].(string); ok { + if v, ok := pointInTime["restore_to_time"].(string); ok { restoreToTime, _ := time.Parse(time.RFC3339, v) createOpts.RestoreToTime = aws.Time(restoreToTime) } From 03b6c0244dc3b62c3de15487263ccd7f32cb0946 Mon Sep 17 00:00:00 2001 From: Gufran Date: Wed, 11 Nov 2020 22:46:54 +0530 Subject: [PATCH 11/14] Check for empty restore_to_time value --- aws/resource_aws_rds_cluster.go | 5 +---- website/docs/r/rds_cluster.html.markdown | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 418ba8f3192..9490e463753 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -706,7 +706,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error Tags: tags, } - if v, ok := pointInTime["restore_to_time"].(string); ok { + if v, ok := pointInTime["restore_to_time"].(string); ok && v != "" { restoreToTime, _ := time.Parse(time.RFC3339, v) createOpts.RestoreToTime = aws.Time(restoreToTime) } @@ -794,9 +794,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error resp, err := conn.RestoreDBClusterToPointInTime(createOpts) if err != nil { log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) - } - - if err != nil { log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) return err } diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index f83fafd3a54..aa22eff6095 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -125,7 +125,7 @@ The following arguments are supported: * `preferred_backup_window` - (Optional) The 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) The 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](/docs/configuration/resources.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/UserGuide/USER_PIT.html). More details below. +* `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. * `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. @@ -181,8 +181,8 @@ resource "aws_rds_cluster" "example-clone" { * `source_cluster_identifier` - (Required) The identifier of the source database cluster from which to restore. * `restore_type` - (Optional) Type of restore to be performed. Valid options are `full-copy` (default) and `copy-on-write`. -* `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. -* `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. +* `use_latest_restorable_time` - (Optional) Set to true to restore the database cluster to the latest restorable backup time. Defaults to false. Conflicts with `restore_to_time`. +* `restore_to_time` - (Optional) Date and time in UTC format to restore the database cluster to. Conflicts with `use_latest_restorable_time`. ### scaling_configuration Argument Reference From eaab9fc6a8145d303c0763684b3a1634cadf4b4f Mon Sep 17 00:00:00 2001 From: Gufran Date: Wed, 11 Nov 2020 22:54:13 +0530 Subject: [PATCH 12/14] Remove input validation check and document --- aws/resource_aws_rds_cluster.go | 22 ---------------------- website/docs/r/rds_cluster.html.markdown | 2 ++ 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 9490e463753..2ffd469834a 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -723,10 +723,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error createOpts.RestoreType = aws.String(attr) } - if err := validateRestoreToPointInTimeInput(d); err != nil { - return err - } - if v, ok := d.GetOk("backtrack_window"); ok { createOpts.BacktrackWindow = aws.Int64(int64(v.(int))) } @@ -970,24 +966,6 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error return resourceAwsRDSClusterRead(d, meta) } -func validateRestoreToPointInTimeInput(d *schema.ResourceData) error { - invalidAttrs := []string{ - "database_name", - "master_username", - "storage_encrypted", - "replication_source_identifier", - "source_region", - } - - for _, attr := range invalidAttrs { - if _, ok := d.GetOk(attr); ok { - return fmt.Errorf(`provider.aws: aws_rds_cluster: %s: %q attribute must not be set for point in time restore operation`, d.Get("database_name"), attr) - } - } - - return nil -} - func resourceAwsRDSClusterRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).rdsconn ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig diff --git a/website/docs/r/rds_cluster.html.markdown b/website/docs/r/rds_cluster.html.markdown index aa22eff6095..156cbc556e9 100644 --- a/website/docs/r/rds_cluster.html.markdown +++ b/website/docs/r/rds_cluster.html.markdown @@ -164,6 +164,8 @@ This will not recreate the resource if the S3 object changes in some way. It's o ### restore_to_point_in_time Argument Reference +~> **NOTE:** The DB cluster is created from the source DB cluster with the same configuration as the original DB cluster, except that the new DB cluster is created with the default DB security group. Thus, the following arguments should only be specified with the source DB cluster's respective values: `database_name`, `master_username`, `storage_encrypted`, `replication_source_identifier`, and `source_region`. + Example: ```hcl From d56f502e802485a0b62a108ebd3f3b0ea7ded31a Mon Sep 17 00:00:00 2001 From: Mohammad Gufran Date: Thu, 12 Nov 2020 11:20:15 +0530 Subject: [PATCH 13/14] Update aws/resource_aws_rds_cluster.go Co-authored-by: angie pinilla --- aws/resource_aws_rds_cluster.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index 2ffd469834a..e786940298b 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -789,10 +789,11 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error resp, err := conn.RestoreDBClusterToPointInTime(createOpts) if err != nil { - log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) return err } + + log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) } else { createOpts := &rds.CreateDBClusterInput{ From 2af971bf9bfc83c3739bfa54f38497fb5c9b9aa2 Mon Sep 17 00:00:00 2001 From: Gufran Date: Thu, 12 Nov 2020 12:18:11 +0530 Subject: [PATCH 14/14] Reformat code --- aws/resource_aws_rds_cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_rds_cluster.go b/aws/resource_aws_rds_cluster.go index e786940298b..6d5ba7d7240 100644 --- a/aws/resource_aws_rds_cluster.go +++ b/aws/resource_aws_rds_cluster.go @@ -792,7 +792,7 @@ func resourceAwsRDSClusterCreate(d *schema.ResourceData, meta interface{}) error log.Printf("[ERROR] Error restoring RDS Cluster: %s", err) return err } - + log.Printf("[DEBUG]: RDS Cluster restore response: %s", resp) } else {