Skip to content

Commit 338ae60

Browse files
committed
providers/aws: Implements DHCP Options Set support.
1 parent a8165fe commit 338ae60

9 files changed

Lines changed: 716 additions & 4 deletions

‎builtin/providers/aws/provider.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ func Provider() terraform.ResourceProvider {
105105
"aws_subnet": resourceAwsSubnet(),
106106
"aws_vpc": resourceAwsVpc(),
107107
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
108+
"aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(),
109+
"aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(),
108110
"aws_vpn_gateway": resourceAwsVpnGateway(),
109111
},
110112

‎builtin/providers/aws/resource_aws_vpc.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ func resourceAwsVpc() *schema.Resource {
5353
Computed: true,
5454
},
5555

56+
"dhcp_options_id": &schema.Schema{
57+
Type: schema.TypeString,
58+
Computed: true,
59+
},
60+
5661
"default_security_group_id": &schema.Schema{
5762
Type: schema.TypeString,
5863
Computed: true,
@@ -126,6 +131,7 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
126131
vpc := vpcRaw.(*ec2.VPC)
127132
vpcid := d.Id()
128133
d.Set("cidr_block", vpc.CIDRBlock)
134+
d.Set("dhcp_options_id", vpc.DHCPOptionsID)
129135

130136
// Tags
131137
d.Set("tags", tagsToMapSDK(vpc.Tags))
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"strings"
7+
"time"
8+
9+
"github.com/awslabs/aws-sdk-go/aws"
10+
"github.com/awslabs/aws-sdk-go/service/ec2"
11+
"github.com/hashicorp/terraform/helper/resource"
12+
"github.com/hashicorp/terraform/helper/schema"
13+
)
14+
15+
func resourceAwsVpcDhcpOptions() *schema.Resource {
16+
return &schema.Resource{
17+
Create: resourceAwsVpcDhcpOptionsCreate,
18+
Read: resourceAwsVpcDhcpOptionsRead,
19+
Update: resourceAwsVpcDhcpOptionsUpdate,
20+
Delete: resourceAwsVpcDhcpOptionsDelete,
21+
22+
Schema: map[string]*schema.Schema{
23+
"domain_name": &schema.Schema{
24+
Type: schema.TypeString,
25+
Optional: true,
26+
ForceNew: true,
27+
},
28+
29+
"domain_name_servers": &schema.Schema{
30+
Type: schema.TypeList,
31+
Optional: true,
32+
ForceNew: true,
33+
Elem: &schema.Schema{Type: schema.TypeString},
34+
},
35+
36+
"ntp_servers": &schema.Schema{
37+
Type: schema.TypeList,
38+
Optional: true,
39+
ForceNew: true,
40+
Elem: &schema.Schema{Type: schema.TypeString},
41+
},
42+
43+
"netbios_node_type": &schema.Schema{
44+
Type: schema.TypeString,
45+
Optional: true,
46+
ForceNew: true,
47+
},
48+
49+
"netbios_name_servers": &schema.Schema{
50+
Type: schema.TypeList,
51+
Optional: true,
52+
ForceNew: true,
53+
Elem: &schema.Schema{Type: schema.TypeString},
54+
},
55+
56+
"tags": &schema.Schema{
57+
Type: schema.TypeMap,
58+
Optional: true,
59+
},
60+
},
61+
}
62+
}
63+
64+
func resourceAwsVpcDhcpOptionsCreate(d *schema.ResourceData, meta interface{}) error {
65+
conn := meta.(*AWSClient).ec2conn
66+
67+
setDHCPOption := func(key string) *ec2.NewDHCPConfiguration {
68+
log.Printf("[DEBUG] Setting DHCP option %s...", key)
69+
tfKey := strings.Replace(key, "-", "_", -1)
70+
71+
value, ok := d.GetOk(tfKey)
72+
if !ok {
73+
return nil
74+
}
75+
76+
if v, ok := value.(string); ok {
77+
return &ec2.NewDHCPConfiguration{
78+
Key: aws.String(key),
79+
Values: []*string{
80+
aws.String(v),
81+
},
82+
}
83+
}
84+
85+
if v, ok := value.([]interface{}); ok {
86+
var s []*string
87+
for _, attr := range v {
88+
s = append(s, aws.String(attr.(string)))
89+
}
90+
91+
return &ec2.NewDHCPConfiguration{
92+
Key: aws.String(key),
93+
Values: s,
94+
}
95+
}
96+
97+
return nil
98+
}
99+
100+
createOpts := &ec2.CreateDHCPOptionsInput{
101+
DHCPConfigurations: []*ec2.NewDHCPConfiguration{
102+
setDHCPOption("domain-name"),
103+
setDHCPOption("domain-name-servers"),
104+
setDHCPOption("ntp-servers"),
105+
setDHCPOption("netbios-node-type"),
106+
setDHCPOption("netbios-name-servers"),
107+
},
108+
}
109+
110+
resp, err := conn.CreateDHCPOptions(createOpts)
111+
if err != nil {
112+
return fmt.Errorf("Error creating DHCP Options Set: %s", err)
113+
}
114+
115+
dos := resp.DHCPOptions
116+
d.SetId(*dos.DHCPOptionsID)
117+
log.Printf("[INFO] DHCP Options Set ID: %s", d.Id())
118+
119+
// Wait for the DHCP Options to become available
120+
log.Printf("[DEBUG] Waiting for DHCP Options (%s) to become available", d.Id())
121+
stateConf := &resource.StateChangeConf{
122+
Pending: []string{"pending"},
123+
Target: "",
124+
Refresh: DHCPOptionsStateRefreshFunc(conn, d.Id()),
125+
Timeout: 1 * time.Minute,
126+
}
127+
if _, err := stateConf.WaitForState(); err != nil {
128+
return fmt.Errorf(
129+
"Error waiting for DHCP Options (%s) to become available: %s",
130+
d.Id(), err)
131+
}
132+
133+
return resourceAwsVpcDhcpOptionsUpdate(d, meta)
134+
}
135+
136+
func resourceAwsVpcDhcpOptionsRead(d *schema.ResourceData, meta interface{}) error {
137+
conn := meta.(*AWSClient).ec2conn
138+
req := &ec2.DescribeDHCPOptionsInput{
139+
DHCPOptionsIDs: []*string{
140+
aws.String(d.Id()),
141+
},
142+
}
143+
144+
resp, err := conn.DescribeDHCPOptions(req)
145+
if err != nil {
146+
return fmt.Errorf("Error retrieving DHCP Options: %s", err)
147+
}
148+
149+
if len(resp.DHCPOptions) == 0 {
150+
return nil
151+
}
152+
153+
opts := resp.DHCPOptions[0]
154+
d.Set("tags", tagsToMapSDK(opts.Tags))
155+
156+
for _, cfg := range opts.DHCPConfigurations {
157+
tfKey := strings.Replace(*cfg.Key, "-", "_", -1)
158+
159+
if _, ok := d.Get(tfKey).(string); ok {
160+
d.Set(tfKey, cfg.Values[0].Value)
161+
} else {
162+
values := make([]string, 0, len(cfg.Values))
163+
for _, v := range cfg.Values {
164+
values = append(values, *v.Value)
165+
}
166+
167+
d.Set(tfKey, values)
168+
}
169+
}
170+
171+
return nil
172+
}
173+
174+
func resourceAwsVpcDhcpOptionsUpdate(d *schema.ResourceData, meta interface{}) error {
175+
conn := meta.(*AWSClient).ec2conn
176+
return setTagsSDK(conn, d)
177+
}
178+
179+
func resourceAwsVpcDhcpOptionsDelete(d *schema.ResourceData, meta interface{}) error {
180+
conn := meta.(*AWSClient).ec2conn
181+
182+
return resource.Retry(3*time.Minute, func() error {
183+
log.Printf("[INFO] Deleting DHCP Options ID %s...", d.Id())
184+
_, err := conn.DeleteDHCPOptions(&ec2.DeleteDHCPOptionsInput{
185+
DHCPOptionsID: aws.String(d.Id()),
186+
})
187+
188+
if err == nil {
189+
return nil
190+
}
191+
192+
log.Printf("[WARN] %s", err)
193+
194+
ec2err, ok := err.(aws.APIError)
195+
if !ok {
196+
return err
197+
}
198+
199+
switch ec2err.Code {
200+
case "InvalidDhcpOptionsID.NotFound":
201+
return nil
202+
case "DependencyViolation":
203+
// If it is a dependency violation, we want to disassociate
204+
// all VPCs using the given DHCP Options ID, and retry deleting.
205+
vpcs, err2 := findVPCsByDHCPOptionsID(conn, d.Id())
206+
if err2 != nil {
207+
log.Printf("[ERROR] %s", err2)
208+
return err2
209+
}
210+
211+
for _, vpc := range vpcs {
212+
log.Printf("[INFO] Disassociating DHCP Options Set %s from VPC %s...", d.Id(), *vpc.VPCID)
213+
if _, err := conn.AssociateDHCPOptions(&ec2.AssociateDHCPOptionsInput{
214+
DHCPOptionsID: aws.String("default"),
215+
VPCID: vpc.VPCID,
216+
}); err != nil {
217+
return err
218+
}
219+
}
220+
return err //retry
221+
default:
222+
// Any other error, we want to quit the retry loop immediately
223+
return resource.RetryError{Err: err}
224+
}
225+
226+
return nil
227+
})
228+
}
229+
230+
func findVPCsByDHCPOptionsID(conn *ec2.EC2, id string) ([]*ec2.VPC, error) {
231+
req := &ec2.DescribeVPCsInput{
232+
Filters: []*ec2.Filter{
233+
&ec2.Filter{
234+
Name: aws.String("dhcp-options-id"),
235+
Values: []*string{
236+
aws.String(id),
237+
},
238+
},
239+
},
240+
}
241+
242+
resp, err := conn.DescribeVPCs(req)
243+
if err != nil {
244+
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpcID.NotFound" {
245+
return nil, nil
246+
}
247+
return nil, err
248+
}
249+
250+
return resp.VPCs, nil
251+
}
252+
253+
func DHCPOptionsStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
254+
return func() (interface{}, string, error) {
255+
DescribeDhcpOpts := &ec2.DescribeDHCPOptionsInput{
256+
DHCPOptionsIDs: []*string{
257+
aws.String(id),
258+
},
259+
}
260+
261+
resp, err := conn.DescribeDHCPOptions(DescribeDhcpOpts)
262+
if err != nil {
263+
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidDhcpOptionsID.NotFound" {
264+
resp = nil
265+
} else {
266+
log.Printf("Error on DHCPOptionsStateRefresh: %s", err)
267+
return nil, "", err
268+
}
269+
}
270+
271+
if resp == nil {
272+
// Sometimes AWS just has consistency issues and doesn't see
273+
// our instance yet. Return an empty state.
274+
return nil, "", nil
275+
}
276+
277+
dos := resp.DHCPOptions[0]
278+
return dos, "", nil
279+
}
280+
}

0 commit comments

Comments
 (0)