diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 4248ba33a..4fc72d72d 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -38,9 +38,10 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ - "aws_eip": resourceAwsEip(), - "aws_instance": resourceAwsInstance(), - "aws_security_group": resourceAwsSecurityGroup(), + "aws_eip": resourceAwsEip(), + "aws_instance": resourceAwsInstance(), + "aws_security_group": resourceAwsSecurityGroup(), + "aws_db_subnet_group": resourceAwsDbSubnetGroup(), }, } } diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index a10dace3b..6e1afa0eb 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -75,6 +75,10 @@ func resource_aws_db_instance_create( opts.PubliclyAccessible = true } + if attr = rs.Attributes["subnet_group_name"]; attr != "" { + opts.DBSubnetGroupName = attr + } + if err != nil { return nil, fmt.Errorf("Error parsing configuration: %s", err) } @@ -223,6 +227,7 @@ func resource_aws_db_instance_diff( "username": diff.AttrTypeCreate, "vpc_security_group_ids": diff.AttrTypeCreate, "security_group_names": diff.AttrTypeCreate, + "subnet_group_name": diff.AttrTypeCreate, "skip_final_snapshot": diff.AttrTypeUpdate, "final_snapshot_identifier": diff.AttrTypeUpdate, }, @@ -265,6 +270,7 @@ func resource_aws_db_instance_update_state( s.Attributes["port"] = strconv.Itoa(v.Port) s.Attributes["status"] = v.DBInstanceStatus s.Attributes["username"] = v.MasterUsername + s.Attributes["subnet_group_name"] = v.DBSubnetGroup.Name // Flatten our group values toFlatten := make(map[string]interface{}) @@ -338,6 +344,7 @@ func resource_aws_db_instance_validation() *config.Validator { "vpc_security_group_ids.*", "skip_final_snapshot", "security_group_names.*", + "subnet_group_name", }, } } diff --git a/builtin/providers/aws/resource_aws_db_subnet_group.go b/builtin/providers/aws/resource_aws_db_subnet_group.go new file mode 100644 index 000000000..b67f5eddc --- /dev/null +++ b/builtin/providers/aws/resource_aws_db_subnet_group.go @@ -0,0 +1,135 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/goamz/rds" +) + +func resourceAwsDbSubnetGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsDbSubnetGroupCreate, + Read: resourceAwsDbSubnetGroupRead, + Update: nil, + Delete: resourceAwsDbSubnetGroupDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "subnet_ids": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceAwsDbSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error { + p := meta.(*ResourceProvider) + rdsconn := p.rdsconn + + subnetIdsSet := d.Get("subnet_ids").(*schema.Set) + subnetIds := make([]string, subnetIdsSet.Len()) + for i, subnetId := range subnetIdsSet.List() { + subnetIds[i] = subnetId.(string) + } + + createOpts := rds.CreateDBSubnetGroup{ + DBSubnetGroupName: d.Get("name").(string), + DBSubnetGroupDescription: d.Get("description").(string), + SubnetIds: subnetIds, + } + + log.Printf("[DEBUG] Create DB Subnet Group: %#v", createOpts) + _, err := rdsconn.CreateDBSubnetGroup(&createOpts) + if err != nil { + return fmt.Errorf("Error creating DB Subnet Group: %s", err) + } + + d.SetId(createOpts.DBSubnetGroupName) + log.Printf("[INFO] DB Subnet Group ID: %s", d.Id()) + return resourceAwsDbSubnetGroupRead(d, meta) +} + +func resourceAwsDbSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: "destroyed", + Refresh: resourceDbSubnetGroupDeleteRefreshFunc(d, meta), + Timeout: 3 * time.Minute, + MinTimeout: 1 * time.Second, + } + _, err := stateConf.WaitForState() + return err +} + +func resourceAwsDbSubnetGroupRead(d *schema.ResourceData, meta interface{}) error { + p := meta.(*ResourceProvider) + rdsconn := p.rdsconn + + describeOpts := rds.DescribeDBSubnetGroups{ + DBSubnetGroupName: d.Id(), + } + + describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts) + if err != nil { + return err + } + + if len(describeResp.DBSubnetGroups) != 1 || + describeResp.DBSubnetGroups[0].Name != d.Id() { + } + + d.Set("name", describeResp.DBSubnetGroups[0].Name) + d.Set("description", describeResp.DBSubnetGroups[0].Description) + d.Set("subnet_ids", describeResp.DBSubnetGroups[0].SubnetIds) + + return nil +} + +func resourceDbSubnetGroupDeleteRefreshFunc( + d *schema.ResourceData, + meta interface{}) resource.StateRefreshFunc { + p := meta.(*ResourceProvider) + rdsconn := p.rdsconn + + return func() (interface{}, string, error) { + + deleteOpts := rds.DeleteDBSubnetGroup{ + DBSubnetGroupName: d.Id(), + } + + if _, err := rdsconn.DeleteDBSubnetGroup(&deleteOpts); err != nil { + rdserr, ok := err.(*rds.Error) + if !ok { + return d, "error", err + } + + if rdserr.Code != "DBSubnetGroupNotFoundFault" { + return d, "error", err + } + } + + return d, "destroyed", nil + } +} diff --git a/builtin/providers/aws/resource_aws_db_subnet_group_test.go b/builtin/providers/aws/resource_aws_db_subnet_group_test.go new file mode 100644 index 000000000..4e959745e --- /dev/null +++ b/builtin/providers/aws/resource_aws_db_subnet_group_test.go @@ -0,0 +1,112 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/goamz/rds" +) + +func TestAccAWSDbSubnetGroup(t *testing.T) { + var v rds.DBSubnetGroup + + testCheck := func(*terraform.State) error { + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDbSubnetGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDbSubnetGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckDbSubnetGroupExists( + "aws_db_subnet_group.foo", &v), + testCheck, + ), + }, + }, + }) +} + +func testAccCheckDbSubnetGroupDestroy(s *terraform.State) error { + conn := testAccProvider.rdsconn + + for _, rs := range s.Resources { + if rs.Type != "aws_db_subnet_group" { + continue + } + + // Try to find the resource + resp, err := conn.DescribeDBSubnetGroups(&rds.DescribeDBSubnetGroups{rs.ID}) + if err == nil { + if len(resp.DBSubnetGroups) > 0 { + return fmt.Errorf("still exist.") + } + + return nil + } + + // Verify the error is what we want + rdserr, ok := err.(*rds.Error) + if !ok { + return err + } + if rdserr.Code != "DBSubnetGroupNotFoundFault" { + return err + } + } + + return nil +} + +func testAccCheckDbSubnetGroupExists(n string, v *rds.DBSubnetGroup) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.rdsconn + resp, err := conn.DescribeDBSubnetGroups(&rds.DescribeDBSubnetGroups{rs.ID}) + if err != nil { + return err + } + if len(resp.DBSubnetGroups) == 0 { + return fmt.Errorf("DbSubnetGroup not found") + } + + *v = resp.DBSubnetGroups[0] + + return nil + } +} + +const testAccDbSubnetGroupConfig = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "foo" { + cidr_block = "10.1.1.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_subnet" "bar" { + cidr_block = "10.1.2.0/24" + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_db_subnet_group" "foo" { + name = "foo" + subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"] +} +`