Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement aws_db_subnet_group resource #164

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions builtin/providers/aws/resource_aws_db_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ func resource_aws_db_instance_create(
rs.Attributes, "security_group_names").([]interface{}))
}

if attr = rs.Attributes["db_subnet_group_name"]; attr != "" {
opts.DBSubnetGroupName = attr
}

opts.DBInstanceIdentifier = rs.Attributes["identifier"]
opts.DBName = rs.Attributes["name"]
opts.MasterUsername = rs.Attributes["username"]
Expand Down Expand Up @@ -227,6 +231,7 @@ func resource_aws_db_instance_diff(
"vpc_security_group_ids": diff.AttrTypeCreate,
"security_group_names": diff.AttrTypeCreate,
"skip_final_snapshot": diff.AttrTypeUpdate,
"db_subnet_group_name": diff.AttrTypeCreate,
"final_snapshot_identifier": diff.AttrTypeUpdate,
},

Expand Down Expand Up @@ -258,6 +263,7 @@ func resource_aws_db_instance_update_state(
s.Attributes["availability_zone"] = v.AvailabilityZone
s.Attributes["backup_retention_period"] = strconv.Itoa(v.BackupRetentionPeriod)
s.Attributes["backup_window"] = v.PreferredBackupWindow
s.Attributes["db_subnet_group_name"] = v.DBSubnetGroup.Name
s.Attributes["endpoint"] = fmt.Sprintf("%s:%s", s.Attributes["address"], strconv.Itoa(v.Port))
s.Attributes["engine"] = v.Engine
s.Attributes["engine_version"] = v.EngineVersion
Expand Down Expand Up @@ -341,6 +347,7 @@ func resource_aws_db_instance_validation() *config.Validator {
"vpc_security_group_ids.*",
"skip_final_snapshot",
"security_group_names.*",
"db_subnet_group_name",
},
}
}
Expand Down
207 changes: 207 additions & 0 deletions builtin/providers/aws/resource_aws_db_subnet_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package aws

import (
"fmt"
"log"
"strings"
"time"

"github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/helper/config"
"github.com/hashicorp/terraform/helper/diff"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/goamz/rds"
)

func resource_aws_db_subnet_group_create(
s *terraform.ResourceState,
d *terraform.ResourceDiff,
meta interface{}) (*terraform.ResourceState, error) {
p := meta.(*ResourceProvider)
conn := p.rdsconn

// Merge the diff into the state so that we have all the attributes
// properly.
rs := s.MergeDiff(d)

var err error

opts := rds.CreateDBSubnetGroup{
DBSubnetGroupName: rs.Attributes["name"],
DBSubnetGroupDescription: rs.Attributes["description"],
SubnetIds: expandStringList(flatmap.Expand(
rs.Attributes, "subnet_ids").([]interface{})),
}

log.Printf("[DEBUG] DB Subnet Group create configuration: %#v", opts)
_, err = conn.CreateDBSubnetGroup(&opts)
if err != nil {
return nil, fmt.Errorf("Error creating DB Subnet Group: %s", err)
}

rs.ID = rs.Attributes["name"]

log.Printf("[INFO] DB Subnet Group ID: %s", rs.ID)

log.Println(
"[INFO] Waiting for DB Subnet Group creation to be complete")

stateConf := &resource.StateChangeConf{
// TODO are there any other states?
Pending: []string{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I imagine this won't work as is, due to this. If we run the acceptance tests it should give us the various states. Are you familiar with how to run those?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but they all pass - the DB subnet group is created so quickly that I can't issue a request fast enough to discover its intermediate state. The docs and a fair amount of googling have turned up no answers, but we have an AWS enterprise support account; I'll ask them. In the meantime, can this be merged, as the lack of intermediate states is fine in practice?

Target: "Complete",
Refresh: DBSubnetGroupStateRefreshFunc(rs.ID, conn),
Timeout: 10 * time.Minute,
}

// Wait, catching any errors
_, err = stateConf.WaitForState()
if err != nil {
return rs, err
}

v, err := resource_aws_db_subnet_group_retrieve(rs.ID, conn)
if err != nil {
return rs, err
}

return resource_aws_db_subnet_group_update_state(rs, v)
}

func resource_aws_db_subnet_group_update(
s *terraform.ResourceState,
d *terraform.ResourceDiff,
meta interface{}) (*terraform.ResourceState, error) {

panic("Cannot update DB Subnet Group")

return nil, nil
}

func resource_aws_db_subnet_group_destroy(
s *terraform.ResourceState,
meta interface{}) error {
p := meta.(*ResourceProvider)
conn := p.rdsconn

log.Printf("[DEBUG] DB Subnet Group destroy: %v", s.ID)

opts := rds.DeleteDBSubnetGroup{DBSubnetGroupName: s.ID}

log.Printf("[DEBUG] DB Subnet Group destroy configuration: %v", opts)
_, err := conn.DeleteDBSubnetGroup(&opts)

if err != nil {
newerr, ok := err.(*rds.Error)
if ok && newerr.Code == "DBSubnetGroupNotFoundFault" {
return nil
}
return err
}

return nil
}

func resource_aws_db_subnet_group_refresh(
s *terraform.ResourceState,
meta interface{}) (*terraform.ResourceState, error) {
p := meta.(*ResourceProvider)
conn := p.rdsconn

v, err := resource_aws_db_subnet_group_retrieve(s.ID, conn)

if err != nil || v == nil {
return s, err
}

return resource_aws_db_subnet_group_update_state(s, v)
}

func resource_aws_db_subnet_group_diff(
s *terraform.ResourceState,
c *terraform.ResourceConfig,
meta interface{}) (*terraform.ResourceDiff, error) {

b := &diff.ResourceBuilder{
Attrs: map[string]diff.AttrType{
"name": diff.AttrTypeCreate,
"description": diff.AttrTypeCreate,
"subnet_ids": diff.AttrTypeCreate,
},
}

return b.Diff(s, c)
}

func resource_aws_db_subnet_group_update_state(
s *terraform.ResourceState,
v *rds.DBSubnetGroup) (*terraform.ResourceState, error) {

s.Attributes["name"] = v.Name
s.Attributes["description"] = v.Description

// Flatten our group values
toFlatten := make(map[string]interface{})

if len(v.SubnetIds) > 0 && v.SubnetIds[0] != "" {
toFlatten["subnet_ids"] = v.SubnetIds
}

for k, v := range flatmap.Flatten(toFlatten) {
s.Attributes[k] = v
}

return s, nil
}

func resource_aws_db_subnet_group_retrieve(name string, conn *rds.Rds) (*rds.DBSubnetGroup, error) {
opts := rds.DescribeDBSubnetGroups{
DBSubnetGroupName: name,
}

log.Printf("[DEBUG] DB Subnet Group describe configuration: %#v", opts)

resp, err := conn.DescribeDBSubnetGroups(&opts)

if err != nil {
if strings.Contains(err.Error(), "DBSubnetGroupNotFound") {
return nil, nil
}
return nil, fmt.Errorf("Error retrieving DB Subnet Groups: %s", err)
}

if len(resp.DBSubnetGroups) != 1 ||
resp.DBSubnetGroups[0].Name != name {
if err != nil {
return nil, nil
}
}

v := resp.DBSubnetGroups[0]

return &v, nil
}

func resource_aws_db_subnet_group_validation() *config.Validator {
return &config.Validator{
Required: []string{
"name",
"description",
"subnet_ids.*",
},
}
}

func DBSubnetGroupStateRefreshFunc(name string, conn *rds.Rds) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
v, err := resource_aws_db_subnet_group_retrieve(name, conn)

if err != nil {
log.Printf("Error on retrieving DB Subnet Group when waiting: %s", err)
return nil, "", err
}

return v, v.Status, nil
}
}
143 changes: 143 additions & 0 deletions builtin/providers/aws/resource_aws_db_subnet_group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
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

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDBSubnetGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDBSubnetGroupConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSDBSubnetGroupExists("aws_db_subnet_group.foo", &v),
testAccCheckAWSDBSubnetGroupAttributes(&v),
resource.TestCheckResourceAttr(
"aws_db_subnet_group.foo", "name", "subgroup-terraform"),
resource.TestCheckResourceAttr(
"aws_db_subnet_group.foo", "description", "just cuz"),
// TODO check subnet ID contents
resource.TestCheckResourceAttr(
"aws_db_subnet_group.foo", "subnet_ids.#", "2"),
),
},
},
})
}

func testAccCheckAWSDBSubnetGroupDestroy(s *terraform.State) error {
conn := testAccProvider.rdsconn

for _, rs := range s.Resources {
if rs.Type != "aws_db_subnet_group" {
continue
}

// Try to find the Group
resp, err := conn.DescribeDBSubnetGroups(
&rds.DescribeDBSubnetGroups{
DBSubnetGroupName: rs.ID,
})

if err == nil {
if len(resp.DBSubnetGroups) != 0 &&
resp.DBSubnetGroups[0].Name == rs.ID {
return fmt.Errorf("DB Subnet Group still exists")
}
}

// Verify the error
_, ok := err.(*rds.Error)
if !ok {
return err
}
}

return nil
}

func testAccCheckAWSDBSubnetGroupAttributes(group *rds.DBSubnetGroup) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(group.SubnetIds) == 0 {
return fmt.Errorf("no subnets: %#v", group.SubnetIds)
}

if group.Name != "subgroup-terraform" {
return fmt.Errorf("bad name: %#v", group.Name)
}

if group.Description != "just cuz" {
return fmt.Errorf("bad description: %#v", group.Description)
}

return nil
}
}

func testAccCheckAWSDBSubnetGroupExists(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 DB Subnet Group ID is set")
}

conn := testAccProvider.rdsconn

opts := rds.DescribeDBSubnetGroups{
DBSubnetGroupName: rs.ID,
}

resp, err := conn.DescribeDBSubnetGroups(&opts)

if err != nil {
return err
}

if len(resp.DBSubnetGroups) != 1 ||
resp.DBSubnetGroups[0].Name != rs.ID {
return fmt.Errorf("DB Subnet Group not found")
}

*v = resp.DBSubnetGroups[0]

return nil
}
}

const testAccAWSDBSubnetGroupConfig = `
resource "aws_vpc" "foo" {
cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "foo" {
cidr_block = "10.0.0.0/24"
vpc_id = "${aws_vpc.foo.id}"
availability_zone = "us-west-2a"
}

resource "aws_subnet" "bar" {
cidr_block = "10.0.1.0/24"
vpc_id = "${aws_vpc.foo.id}"
availability_zone = "us-west-2b"
}

resource "aws_db_subnet_group" "foo" {
name = "subgroup-terraform"
description = "just cuz"
subnet_ids = ["${aws_subnet.foo.id}", "${aws_subnet.bar.id}"]
}
`
Loading