diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 34e78184c6..5f865a72a7 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -26,6 +26,7 @@ func resourceAwsDbInstance() *schema.Resource { "name": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, @@ -89,6 +90,7 @@ func resourceAwsDbInstance() *schema.Resource { "backup_retention_period": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Computed: true, Default: 1, }, @@ -191,6 +193,17 @@ func resourceAwsDbInstance() *schema.Resource { Computed: true, }, + "replicate_source_db": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "replicas": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tagsSchema(), }, } @@ -199,87 +212,113 @@ func resourceAwsDbInstance() *schema.Resource { func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).rdsconn tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) - opts := rds.CreateDBInstanceInput{ - AllocatedStorage: aws.Long(int64(d.Get("allocated_storage").(int))), - DBInstanceClass: aws.String(d.Get("instance_class").(string)), - DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), - DBName: aws.String(d.Get("name").(string)), - MasterUsername: aws.String(d.Get("username").(string)), - MasterUserPassword: aws.String(d.Get("password").(string)), - Engine: aws.String(d.Get("engine").(string)), - EngineVersion: aws.String(d.Get("engine_version").(string)), - StorageEncrypted: aws.Boolean(d.Get("storage_encrypted").(bool)), - Tags: tags, - } - if attr, ok := d.GetOk("storage_type"); ok { - opts.StorageType = aws.String(attr.(string)) - } - - attr := d.Get("backup_retention_period") - opts.BackupRetentionPeriod = aws.Long(int64(attr.(int))) - - if attr, ok := d.GetOk("iops"); ok { - opts.IOPS = aws.Long(int64(attr.(int))) - } - - if attr, ok := d.GetOk("port"); ok { - opts.Port = aws.Long(int64(attr.(int))) - } - - if attr, ok := d.GetOk("multi_az"); ok { - opts.MultiAZ = aws.Boolean(attr.(bool)) - } - - if attr, ok := d.GetOk("availability_zone"); ok { - opts.AvailabilityZone = aws.String(attr.(string)) - } - - if attr, ok := d.GetOk("license_model"); ok { - opts.LicenseModel = aws.String(attr.(string)) - } - - if attr, ok := d.GetOk("maintenance_window"); ok { - opts.PreferredMaintenanceWindow = aws.String(attr.(string)) - } - - if attr, ok := d.GetOk("backup_window"); ok { - opts.PreferredBackupWindow = aws.String(attr.(string)) - } - - if attr, ok := d.GetOk("publicly_accessible"); ok { - opts.PubliclyAccessible = aws.Boolean(attr.(bool)) - } - - if attr, ok := d.GetOk("db_subnet_group_name"); ok { - opts.DBSubnetGroupName = aws.String(attr.(string)) - } - - if attr, ok := d.GetOk("parameter_group_name"); ok { - opts.DBParameterGroupName = aws.String(attr.(string)) - } - - if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { - var s []*string - for _, v := range attr.List() { - s = append(s, aws.String(v.(string))) + if v, ok := d.GetOk("replicate_source_db"); ok { + opts := rds.CreateDBInstanceReadReplicaInput{ + SourceDBInstanceIdentifier: aws.String(v.(string)), + DBInstanceClass: aws.String(d.Get("instance_class").(string)), + DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), + Tags: tags, } - opts.VPCSecurityGroupIDs = s - } - - if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 { - var s []*string - for _, v := range attr.List() { - s = append(s, aws.String(v.(string))) + if attr, ok := d.GetOk("iops"); ok { + opts.IOPS = aws.Long(int64(attr.(int))) } - opts.DBSecurityGroups = s - } - log.Printf("[DEBUG] DB Instance create configuration: %#v", opts) - var err error - _, err = conn.CreateDBInstance(&opts) - if err != nil { - return fmt.Errorf("Error creating DB Instance: %s", err) + if attr, ok := d.GetOk("port"); ok { + opts.Port = aws.Long(int64(attr.(int))) + } + + if attr, ok := d.GetOk("availability_zone"); ok { + opts.AvailabilityZone = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("publicly_accessible"); ok { + opts.PubliclyAccessible = aws.Boolean(attr.(bool)) + } + _, err := conn.CreateDBInstanceReadReplica(&opts) + if err != nil { + return fmt.Errorf("Error creating DB Instance: %s", err) + } + } else { + opts := rds.CreateDBInstanceInput{ + AllocatedStorage: aws.Long(int64(d.Get("allocated_storage").(int))), + DBName: aws.String(d.Get("name").(string)), + DBInstanceClass: aws.String(d.Get("instance_class").(string)), + DBInstanceIdentifier: aws.String(d.Get("identifier").(string)), + MasterUsername: aws.String(d.Get("username").(string)), + MasterUserPassword: aws.String(d.Get("password").(string)), + Engine: aws.String(d.Get("engine").(string)), + EngineVersion: aws.String(d.Get("engine_version").(string)), + StorageEncrypted: aws.Boolean(d.Get("storage_encrypted").(bool)), + Tags: tags, + } + + attr := d.Get("backup_retention_period") + opts.BackupRetentionPeriod = aws.Long(int64(attr.(int))) + if attr, ok := d.GetOk("multi_az"); ok { + opts.MultiAZ = aws.Boolean(attr.(bool)) + } + + if attr, ok := d.GetOk("maintenance_window"); ok { + opts.PreferredMaintenanceWindow = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("backup_window"); ok { + opts.PreferredBackupWindow = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("license_model"); ok { + opts.LicenseModel = aws.String(attr.(string)) + } + if attr, ok := d.GetOk("parameter_group_name"); ok { + opts.DBParameterGroupName = aws.String(attr.(string)) + } + + if attr := d.Get("vpc_security_group_ids").(*schema.Set); attr.Len() > 0 { + var s []*string + for _, v := range attr.List() { + s = append(s, aws.String(v.(string))) + } + opts.VPCSecurityGroupIDs = s + } + + if attr := d.Get("security_group_names").(*schema.Set); attr.Len() > 0 { + var s []*string + for _, v := range attr.List() { + s = append(s, aws.String(v.(string))) + } + opts.DBSecurityGroups = s + } + if attr, ok := d.GetOk("storage_type"); ok { + opts.StorageType = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("db_subnet_group_name"); ok { + opts.DBSubnetGroupName = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("iops"); ok { + opts.IOPS = aws.Long(int64(attr.(int))) + } + + if attr, ok := d.GetOk("port"); ok { + opts.Port = aws.Long(int64(attr.(int))) + } + + if attr, ok := d.GetOk("availability_zone"); ok { + opts.AvailabilityZone = aws.String(attr.(string)) + } + + if attr, ok := d.GetOk("publicly_accessible"); ok { + opts.PubliclyAccessible = aws.Boolean(attr.(bool)) + } + + log.Printf("[DEBUG] DB Instance create configuration: %#v", opts) + var err error + _, err = conn.CreateDBInstance(&opts) + if err != nil { + return fmt.Errorf("Error creating DB Instance: %s", err) + } } d.SetId(d.Get("identifier").(string)) @@ -299,7 +338,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error } // Wait, catching any errors - _, err = stateConf.WaitForState() + _, err := stateConf.WaitForState() if err != nil { return err } @@ -397,6 +436,23 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { } d.Set("security_group_names", sgn) + // replica things + + var replicas []string + for _, v := range v.ReadReplicaDBInstanceIdentifiers { + replicas = append(replicas, *v) + } + if err := d.Set("replicas", replicas); err != nil { + return fmt.Errorf("[DEBUG] Error setting replicas attribute: %#v, error: %#v", replicas, err) + } + + if v.ReadReplicaSourceDBInstanceIdentifier != nil { + log.Printf("\n\n------\nread replica instance identifier: %#v", *v.ReadReplicaSourceDBInstanceIdentifier) + } else { + log.Printf("\n\n------\nno replica identifier") + } + d.Set("replicate_source_db", v.ReadReplicaSourceDBInstanceIdentifier) + return nil } @@ -438,6 +494,7 @@ func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error } func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("\n\n-------- ENTER UPDATE -------\n\n") conn := meta.(*AWSClient).rdsconn d.Partial(true) @@ -536,6 +593,28 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error } } + // seperate request to promote a database + if d.HasChange("replicate_source_db") { + if d.Get("replicate_source_db").(string) == "" { + // promote + opts := rds.PromoteReadReplicaInput{ + DBInstanceIdentifier: aws.String(d.Id()), + } + attr := d.Get("backup_retention_period") + opts.BackupRetentionPeriod = aws.Long(int64(attr.(int))) + if attr, ok := d.GetOk("backup_window"); ok { + opts.PreferredBackupWindow = aws.String(attr.(string)) + } + _, err := conn.PromoteReadReplica(&opts) + if err != nil { + return fmt.Errorf("Error promoting database: %#v", err) + } + d.Set("replicate_source_db", "") + } else { + return fmt.Errorf("cannot elect new source database for replication") + } + } + if arn, err := buildRDSARN(d, meta); err == nil { if err := setTagsRDS(conn, d, arn); err != nil { return err @@ -544,6 +623,7 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error } } d.Partial(false) + log.Printf("\n\n-------- EXIT UPDATE -------\n\n") return resourceAwsDbInstanceRead(d, meta) } diff --git a/builtin/providers/aws/resource_aws_db_instance_test.go b/builtin/providers/aws/resource_aws_db_instance_test.go index 3b81817da1..b5ddf42a74 100644 --- a/builtin/providers/aws/resource_aws_db_instance_test.go +++ b/builtin/providers/aws/resource_aws_db_instance_test.go @@ -49,6 +49,35 @@ func TestAccAWSDBInstance(t *testing.T) { }) } +func TestAccAWSDBInstanceReplica(t *testing.T) { + // var v rds.DBInstance + var r rds.DBInstance + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSDBInstanceDestroy, + Steps: []resource.TestStep{ + // resource.TestStep{ + // Config: testAccAWSDBInstanceConfig, + // Check: resource.ComposeTestCheckFunc( + // testAccCheckAWSDBInstanceExists("aws_db_instance.bar", &v), + // ), + // }, + + resource.TestStep{ + Config: testAccAWSDBInstanceReplicaConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSDBInstanceExists("aws_db_instance.replica", &r), + resource.TestCheckResourceAttr( + "aws_db_instance.replica", "replicate_source_db", "tf-update-more"), + testAccCheckAWSDBInstanceReplicaAttributes(&r), + ), + }, + }, + }) +} + func testAccCheckAWSDBInstanceDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).rdsconn @@ -103,6 +132,29 @@ func testAccCheckAWSDBInstanceAttributes(v *rds.DBInstance) resource.TestCheckFu } } +func testAccCheckAWSDBInstanceReplicaAttributes(r *rds.DBInstance) resource.TestCheckFunc { + return func(s *terraform.State) error { + + // if *v.Engine != "mysql" { + // return fmt.Errorf("bad engine: %#v", *v.Engine) + // } + + // if *v.EngineVersion != "5.6.21" { + // return fmt.Errorf("bad engine_version: %#v", *v.EngineVersion) + // } + + // if *v.BackupRetentionPeriod != 0 { + // return fmt.Errorf("bad backup_retention_period: %#v", *v.BackupRetentionPeriod) + // } + + if r.ReadReplicaSourceDBInstanceIdentifier != nil && *r.ReadReplicaSourceDBInstanceIdentifier != "tf-update-more" { + return fmt.Errorf("bad source identifier for replica, got: %#v, expected: 'tf-update-more'", *r.ReadReplicaSourceDBInstanceIdentifier) + } + + return nil + } +} + func testAccCheckAWSDBInstanceExists(n string, v *rds.DBInstance) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -156,3 +208,18 @@ resource "aws_db_instance" "bar" { parameter_group_name = "default.mysql5.6" }`, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) + +var testAccAWSDBInstanceReplicaConfig = fmt.Sprintf(` +resource "aws_db_instance" "replica" { + identifier = "tf-replica-db-%d" + replicate_source_db = "tf-update-more" + allocated_storage = 5 + engine = "mysql" + engine_version = "5.6.19b" + instance_class = "db.t1.micro" + password = "fofofofxx" + username = "foo" + tags { + Name = "tf-replica-db" + } +}`, rand.New(rand.NewSource(time.Now().UnixNano())).Int())