diff --git a/aws/resource_aws_db_instance.go b/aws/resource_aws_db_instance.go index b52d856a1e2..8eb68f4b7aa 100644 --- a/aws/resource_aws_db_instance.go +++ b/aws/resource_aws_db_instance.go @@ -8,6 +8,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/rds" @@ -387,6 +388,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error PubliclyAccessible: aws.Bool(d.Get("publicly_accessible").(bool)), Tags: tags, } + if attr, ok := d.GetOk("iops"); ok { opts.Iops = aws.Int64(int64(attr.(int))) } @@ -399,6 +401,34 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.AvailabilityZone = aws.String(attr.(string)) } + // + // If we are called with a Source DB ARN, and the ARN is a different region + // than the replica to be created, set SourceRegion. + // + // The correct way to do this would be to query the master, and see if it + // is encrypted and in the same region. If it is encrypted and in the + // same region, drop the source region and the kms_key_id. If the master is not + // encrypted, behavior is kinda undefined. + // + // The CLI docs for kms_key_id state: + // "If you specify this parameter when you create a Read Replica from an + // unencrypted DB instance, the Read Replica is encrypted."" + // + // The RDS userguide states: + // "You cannot have an encrypted Read Replica of an unencrypted DB instance + // or an unencrypted Read Replica of an encrypted DB instance." + // + // go figure, eh? + // + replicaRegion := meta.(*AWSClient).region + + arnParts, arnErr := arn.Parse(d.Get("replicate_source_db").(string)) + if arnErr == nil { + if arnParts.Region != replicaRegion { + opts.SourceRegion = aws.String(arnParts.Region) + } + } + if attr, ok := d.GetOk("storage_type"); ok { opts.StorageType = aws.String(attr.(string)) } @@ -407,11 +437,11 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error opts.DBSubnetGroupName = aws.String(attr.(string)) } + // TODO: Only allow this param if the master is not encrypted or + // is in a different region than the replica + if attr, ok := d.GetOk("kms_key_id"); ok { opts.KmsKeyId = aws.String(attr.(string)) - if arnParts := strings.Split(v.(string), ":"); len(arnParts) >= 4 { - opts.SourceRegion = aws.String(arnParts[3]) - } } if attr, ok := d.GetOk("monitoring_role_arn"); ok { @@ -777,7 +807,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { // list tags for resource // set tags conn := meta.(*AWSClient).rdsconn - arn, err := buildRDSARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) + builtArn, err := buildRDSARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) if err != nil { name := "" if v.DBName != nil && *v.DBName != "" { @@ -785,13 +815,13 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { } log.Printf("[DEBUG] Error building ARN for DB Instance, not setting Tags for DB %s", name) } else { - d.Set("arn", arn) + d.Set("arn", builtArn) resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{ - ResourceName: aws.String(arn), + ResourceName: aws.String(builtArn), }) if err != nil { - log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) + log.Printf("[DEBUG] Error retrieving tags for ARN: %s", builtArn) } var dt []*rds.Tag @@ -828,7 +858,13 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("[DEBUG] Error setting replicas attribute: %#v, error: %#v", replicas, err) } - d.Set("replicate_source_db", v.ReadReplicaSourceDBInstanceIdentifier) + // If an ARN was passed in, do NOT use what AWS passes back for replicate_source_id, + // as it passes back the master's ID- + // see https://github.com/terraform-providers/terraform-provider-aws/issues/2399 + _, arnErr := arn.Parse(d.Get("replicate_source_db").(string)) + if arnErr != nil { + d.Set("replicate_source_db", v.ReadReplicaSourceDBInstanceIdentifier) + } d.Set("ca_cert_identifier", v.CACertificateIdentifier) diff --git a/aws/resource_aws_db_instance_test.go b/aws/resource_aws_db_instance_test.go index 6dfb8e15e2f..a9161fcf9e5 100644 --- a/aws/resource_aws_db_instance_test.go +++ b/aws/resource_aws_db_instance_test.go @@ -266,6 +266,7 @@ func TestAccAWSDBInstance_replica(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &s), testAccCheckAWSDBInstanceExists("aws_db_instance.replica", &r), + resource.TestCheckNoResourceAttr("aws_db_instance.replica", "source_region"), testAccCheckAWSDBInstanceReplicaAttributes(&s, &r), ), }, @@ -273,7 +274,72 @@ func TestAccAWSDBInstance_replica(t *testing.T) { }) } -func TestAccAWSDBInstance_noSnapshot(t *testing.T) { +func TestAccAWSDBInstanceReplicaSameRegionWithArn(t *testing.T) { + var s, r rds.DBInstance + var id int + + region := "us-east-1" + id = rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccReplicaInstanceSameRegionWithArnConfig( + id, + fmt.Sprintf("arn:aws:rds:%s:%s:db:foobarbaz-test-terraform-%v", + region, + "123456789012", + id, + ), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &s), + testAccCheckAWSDBInstanceExists("aws_db_instance.replica", &r), + resource.TestCheckNoResourceAttr("aws_db_instance.replica", "source_region"), + testAccCheckAWSDBInstanceReplicaAttributes(&s, &r), + ), + }, + }, + }) +} + +func TestAccAWSDBInstanceReplicaCrossRegion(t *testing.T) { + var s, r rds.DBInstance + var id int + + region := "us-east-1" + id = rand.New(rand.NewSource(time.Now().UnixNano())).Int() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccReplicaInstanceCrossRegionConfig( + id, + fmt.Sprintf("arn:aws:rds:%s:%s:db:foobarbaz-test-terraform-%v", + region, + "123456789012", + id, + ), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &s), + testAccCheckAWSDBInstanceExists("aws_db_instance.replica", &r), + testAccCheckAWSDBInstanceReplicaAttributes(&s, &r), + resource.TestCheckResourceAttr( + "aws_db_instance.replica", "source_region", region), + ), + }, + }, + }) +} + +func TestAccAWSDBInstanceNoSnapshot(t *testing.T) { var snap rds.DBInstance resource.Test(t, resource.TestCase{ @@ -931,6 +997,84 @@ func testAccReplicaInstanceConfig(val int) string { `, val, val) } +func testAccReplicaInstanceSameRegionWithArnConfig(val int, arn string) string { + return fmt.Sprintf(` + resource "aws_db_instance" "bar" { + identifier = "%s" + + allocated_storage = 5 + engine = "mysql" + engine_version = "5.6.35" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + availability_zone = "us-east-1a" + + backup_retention_period = 1 + skip_final_snapshot = true + + parameter_group_name = "default.mysql5.6" + } + + resource "aws_db_instance" "replica" { + identifier = "tf-replica-db-%d" + backup_retention_period = 0 + availability_zone = "us-east-1a" + replicate_source_db = "${aws_db_instance.bar.identifier}" + allocated_storage = "${aws_db_instance.bar.allocated_storage}" + engine = "${aws_db_instance.bar.engine}" + engine_version = "${aws_db_instance.bar.engine_version}" + instance_class = "${aws_db_instance.bar.instance_class}" + password = "${aws_db_instance.bar.password}" + username = "${aws_db_instance.bar.username}" + skip_final_snapshot = true + tags { + Name = "tf-replica-db" + } + } + `, arn, val) +} + +func testAccReplicaInstanceCrossRegionConfig(val int, arn string) string { + return fmt.Sprintf(` + resource "aws_db_instance" "bar" { + identifier = "%s" + + allocated_storage = 5 + engine = "mysql" + engine_version = "5.6.35" + instance_class = "db.t2.micro" + name = "baz" + password = "barbarbarbar" + username = "foo" + availability_zone = "us-east-1a" + + backup_retention_period = 1 + skip_final_snapshot = true + + parameter_group_name = "default.mysql5.6" + } + + resource "aws_db_instance" "replica" { + identifier = "tf-replica-db-%d" + backup_retention_period = 0 + availability_zone = "us-east-2a" + replicate_source_db = "${aws_db_instance.bar.identifier}" + allocated_storage = "${aws_db_instance.bar.allocated_storage}" + engine = "${aws_db_instance.bar.engine}" + engine_version = "${aws_db_instance.bar.engine_version}" + instance_class = "${aws_db_instance.bar.instance_class}" + password = "${aws_db_instance.bar.password}" + username = "${aws_db_instance.bar.username}" + skip_final_snapshot = true + tags { + Name = "tf-replica-db" + } + } + `, arn, val) +} + func testAccSnapshotInstanceConfig() string { return fmt.Sprintf(` resource "aws_db_instance" "snapshot" {