Skip to content

Commit

Permalink
Merge pull request #1721 from managedbyq/aws_dhcp_options
Browse files Browse the repository at this point in the history
providers/aws: Implements DHCP Options Set support.
  • Loading branch information
phinze committed Apr 30, 2015
2 parents a8165fe + 338ae60 commit d230cfb
Show file tree
Hide file tree
Showing 9 changed files with 716 additions and 4 deletions.
2 changes: 2 additions & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func Provider() terraform.ResourceProvider {
"aws_subnet": resourceAwsSubnet(),
"aws_vpc": resourceAwsVpc(),
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
"aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(),
"aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(),
"aws_vpn_gateway": resourceAwsVpnGateway(),
},

Expand Down
6 changes: 6 additions & 0 deletions builtin/providers/aws/resource_aws_vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ func resourceAwsVpc() *schema.Resource {
Computed: true,
},

"dhcp_options_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"default_security_group_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -126,6 +131,7 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
vpc := vpcRaw.(*ec2.VPC)
vpcid := d.Id()
d.Set("cidr_block", vpc.CIDRBlock)
d.Set("dhcp_options_id", vpc.DHCPOptionsID)

// Tags
d.Set("tags", tagsToMapSDK(vpc.Tags))
Expand Down
280 changes: 280 additions & 0 deletions builtin/providers/aws/resource_aws_vpc_dhcp_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package aws

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

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsVpcDhcpOptions() *schema.Resource {
return &schema.Resource{
Create: resourceAwsVpcDhcpOptionsCreate,
Read: resourceAwsVpcDhcpOptionsRead,
Update: resourceAwsVpcDhcpOptionsUpdate,
Delete: resourceAwsVpcDhcpOptionsDelete,

Schema: map[string]*schema.Schema{
"domain_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"domain_name_servers": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"ntp_servers": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"netbios_node_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"netbios_name_servers": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"tags": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
},
}
}

func resourceAwsVpcDhcpOptionsCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

setDHCPOption := func(key string) *ec2.NewDHCPConfiguration {
log.Printf("[DEBUG] Setting DHCP option %s...", key)
tfKey := strings.Replace(key, "-", "_", -1)

value, ok := d.GetOk(tfKey)
if !ok {
return nil
}

if v, ok := value.(string); ok {
return &ec2.NewDHCPConfiguration{
Key: aws.String(key),
Values: []*string{
aws.String(v),
},
}
}

if v, ok := value.([]interface{}); ok {
var s []*string
for _, attr := range v {
s = append(s, aws.String(attr.(string)))
}

return &ec2.NewDHCPConfiguration{
Key: aws.String(key),
Values: s,
}
}

return nil
}

createOpts := &ec2.CreateDHCPOptionsInput{
DHCPConfigurations: []*ec2.NewDHCPConfiguration{
setDHCPOption("domain-name"),
setDHCPOption("domain-name-servers"),
setDHCPOption("ntp-servers"),
setDHCPOption("netbios-node-type"),
setDHCPOption("netbios-name-servers"),
},
}

resp, err := conn.CreateDHCPOptions(createOpts)
if err != nil {
return fmt.Errorf("Error creating DHCP Options Set: %s", err)
}

dos := resp.DHCPOptions
d.SetId(*dos.DHCPOptionsID)
log.Printf("[INFO] DHCP Options Set ID: %s", d.Id())

// Wait for the DHCP Options to become available
log.Printf("[DEBUG] Waiting for DHCP Options (%s) to become available", d.Id())
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: "",
Refresh: DHCPOptionsStateRefreshFunc(conn, d.Id()),
Timeout: 1 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf(
"Error waiting for DHCP Options (%s) to become available: %s",
d.Id(), err)
}

return resourceAwsVpcDhcpOptionsUpdate(d, meta)
}

func resourceAwsVpcDhcpOptionsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
req := &ec2.DescribeDHCPOptionsInput{
DHCPOptionsIDs: []*string{
aws.String(d.Id()),
},
}

resp, err := conn.DescribeDHCPOptions(req)
if err != nil {
return fmt.Errorf("Error retrieving DHCP Options: %s", err)
}

if len(resp.DHCPOptions) == 0 {
return nil
}

opts := resp.DHCPOptions[0]
d.Set("tags", tagsToMapSDK(opts.Tags))

for _, cfg := range opts.DHCPConfigurations {
tfKey := strings.Replace(*cfg.Key, "-", "_", -1)

if _, ok := d.Get(tfKey).(string); ok {
d.Set(tfKey, cfg.Values[0].Value)
} else {
values := make([]string, 0, len(cfg.Values))
for _, v := range cfg.Values {
values = append(values, *v.Value)
}

d.Set(tfKey, values)
}
}

return nil
}

func resourceAwsVpcDhcpOptionsUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
return setTagsSDK(conn, d)
}

func resourceAwsVpcDhcpOptionsDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

return resource.Retry(3*time.Minute, func() error {
log.Printf("[INFO] Deleting DHCP Options ID %s...", d.Id())
_, err := conn.DeleteDHCPOptions(&ec2.DeleteDHCPOptionsInput{
DHCPOptionsID: aws.String(d.Id()),
})

if err == nil {
return nil
}

log.Printf("[WARN] %s", err)

ec2err, ok := err.(aws.APIError)
if !ok {
return err
}

switch ec2err.Code {
case "InvalidDhcpOptionsID.NotFound":
return nil
case "DependencyViolation":
// If it is a dependency violation, we want to disassociate
// all VPCs using the given DHCP Options ID, and retry deleting.
vpcs, err2 := findVPCsByDHCPOptionsID(conn, d.Id())
if err2 != nil {
log.Printf("[ERROR] %s", err2)
return err2
}

for _, vpc := range vpcs {
log.Printf("[INFO] Disassociating DHCP Options Set %s from VPC %s...", d.Id(), *vpc.VPCID)
if _, err := conn.AssociateDHCPOptions(&ec2.AssociateDHCPOptionsInput{
DHCPOptionsID: aws.String("default"),
VPCID: vpc.VPCID,
}); err != nil {
return err
}
}
return err //retry
default:
// Any other error, we want to quit the retry loop immediately
return resource.RetryError{Err: err}
}

return nil
})
}

func findVPCsByDHCPOptionsID(conn *ec2.EC2, id string) ([]*ec2.VPC, error) {
req := &ec2.DescribeVPCsInput{
Filters: []*ec2.Filter{
&ec2.Filter{
Name: aws.String("dhcp-options-id"),
Values: []*string{
aws.String(id),
},
},
},
}

resp, err := conn.DescribeVPCs(req)
if err != nil {
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpcID.NotFound" {
return nil, nil
}
return nil, err
}

return resp.VPCs, nil
}

func DHCPOptionsStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
DescribeDhcpOpts := &ec2.DescribeDHCPOptionsInput{
DHCPOptionsIDs: []*string{
aws.String(id),
},
}

resp, err := conn.DescribeDHCPOptions(DescribeDhcpOpts)
if err != nil {
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidDhcpOptionsID.NotFound" {
resp = nil
} else {
log.Printf("Error on DHCPOptionsStateRefresh: %s", err)
return nil, "", err
}
}

if resp == nil {
// Sometimes AWS just has consistency issues and doesn't see
// our instance yet. Return an empty state.
return nil, "", nil
}

dos := resp.DHCPOptions[0]
return dos, "", nil
}
}
Loading

0 comments on commit d230cfb

Please sign in to comment.