Lokasi ngalangkungan proxy:   [ UP ]  
[Ngawartoskeun bug]   [Panyetelan cookie]                
Skip to content

Commit 814403f

Browse files
committed
aws: Add support for AWS VPN connections
1 parent 5d07394 commit 814403f

2 files changed

Lines changed: 357 additions & 0 deletions

File tree

builtin/providers/aws/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func Provider() terraform.ResourceProvider {
107107
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
108108
"aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(),
109109
"aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(),
110+
"aws_vpn_connection": resourceAwsVpnConnection(),
110111
"aws_vpn_gateway": resourceAwsVpnGateway(),
111112
},
112113

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
package aws
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"time"
8+
9+
"github.com/awslabs/aws-sdk-go/aws"
10+
"github.com/awslabs/aws-sdk-go/service/ec2"
11+
12+
"github.com/hashicorp/terraform/helper/hashcode"
13+
"github.com/hashicorp/terraform/helper/resource"
14+
"github.com/hashicorp/terraform/helper/schema"
15+
)
16+
17+
func resourceAwsVpnConnection() *schema.Resource {
18+
return &schema.Resource{
19+
Create: resourceAwsVpnConnectionCreate,
20+
Read: resourceAwsVpnConnectionRead,
21+
Update: resourceAwsVpnConnectionUpdate,
22+
Delete: resourceAwsVpnConnectionDelete,
23+
24+
Schema: map[string]*schema.Schema{
25+
"vpn_gateway_id": &schema.Schema{
26+
Type: schema.TypeString,
27+
Required: true,
28+
ForceNew: true,
29+
},
30+
31+
"customer_gateway_id": &schema.Schema{
32+
Type: schema.TypeString,
33+
Required: true,
34+
ForceNew: true,
35+
},
36+
37+
"type": &schema.Schema{
38+
Type: schema.TypeString,
39+
Required: true,
40+
ForceNew: true,
41+
},
42+
43+
"static_routes_only": &schema.Schema{
44+
Type: schema.TypeBool,
45+
Required: true,
46+
ForceNew: true,
47+
},
48+
49+
"tags": tagsSchema(),
50+
51+
// Begin read only attributes
52+
"customer_gateway_configuration": &schema.Schema{
53+
Type: schema.TypeString,
54+
Computed: true,
55+
Optional: true,
56+
},
57+
58+
"routes": &schema.Schema{
59+
Type: schema.TypeSet,
60+
Computed: true,
61+
Optional: true,
62+
Elem: &schema.Resource{
63+
Schema: map[string]*schema.Schema{
64+
"destination_cidr_block": &schema.Schema{
65+
Type: schema.TypeString,
66+
Computed: true,
67+
Optional: true,
68+
},
69+
70+
"source": &schema.Schema{
71+
Type: schema.TypeString,
72+
Computed: true,
73+
Optional: true,
74+
},
75+
76+
"state": &schema.Schema{
77+
Type: schema.TypeString,
78+
Computed: true,
79+
Optional: true,
80+
},
81+
},
82+
},
83+
Set: func(v interface{}) int {
84+
var buf bytes.Buffer
85+
m := v.(map[string]interface{})
86+
buf.WriteString(fmt.Sprintf("%s-", m["destination_cidr_block"].(string)))
87+
buf.WriteString(fmt.Sprintf("%s-", m["source"].(string)))
88+
buf.WriteString(fmt.Sprintf("%s-", m["state"].(string)))
89+
return hashcode.String(buf.String())
90+
},
91+
},
92+
93+
"vgw_telemetry": &schema.Schema{
94+
Type: schema.TypeSet,
95+
Computed: true,
96+
Optional: true,
97+
Elem: &schema.Resource{
98+
Schema: map[string]*schema.Schema{
99+
"accepted_route_count": &schema.Schema{
100+
Type: schema.TypeInt,
101+
Computed: true,
102+
Optional: true,
103+
},
104+
105+
"last_status_change": &schema.Schema{
106+
Type: schema.TypeString,
107+
Computed: true,
108+
Optional: true,
109+
},
110+
111+
"outside_ip_address": &schema.Schema{
112+
Type: schema.TypeString,
113+
Computed: true,
114+
Optional: true,
115+
},
116+
117+
"status": &schema.Schema{
118+
Type: schema.TypeString,
119+
Computed: true,
120+
Optional: true,
121+
},
122+
123+
"status_message": &schema.Schema{
124+
Type: schema.TypeString,
125+
Computed: true,
126+
Optional: true,
127+
},
128+
},
129+
},
130+
Set: func(v interface{}) int {
131+
var buf bytes.Buffer
132+
m := v.(map[string]interface{})
133+
buf.WriteString(fmt.Sprintf("%s-", m["outside_ip_address"].(string)))
134+
return hashcode.String(buf.String())
135+
},
136+
},
137+
},
138+
}
139+
}
140+
141+
func resourceAwsVpnConnectionCreate(d *schema.ResourceData, meta interface{}) error {
142+
conn := meta.(*AWSClient).ec2conn
143+
144+
connectOpts := &ec2.VPNConnectionOptionsSpecification{
145+
StaticRoutesOnly: aws.Boolean(d.Get("static_routes_only").(bool)),
146+
}
147+
148+
createOpts := &ec2.CreateVPNConnectionInput{
149+
CustomerGatewayID: aws.String(d.Get("customer_gateway_id").(string)),
150+
Options: connectOpts,
151+
Type: aws.String(d.Get("type").(string)),
152+
VPNGatewayID: aws.String(d.Get("vpn_gateway_id").(string)),
153+
}
154+
155+
// Create the VPN Connection
156+
log.Printf("[DEBUG] Creating vpn connection")
157+
resp, err := conn.CreateVPNConnection(createOpts)
158+
if err != nil {
159+
return fmt.Errorf("Error creating vpn connection: %s", err)
160+
}
161+
162+
// Store the ID
163+
vpnConnection := resp.VPNConnection
164+
d.SetId(*vpnConnection.VPNConnectionID)
165+
log.Printf("[INFO] VPN connection ID: %s", *vpnConnection.VPNConnectionID)
166+
167+
// Wait for the connection to become available. This has an obscenely
168+
// high default timeout because AWS VPN connections are notoriously
169+
// slow at coming up or going down. There's also no point in checking
170+
// more frequently than every ten seconds.
171+
stateConf := &resource.StateChangeConf{
172+
Pending: []string{"pending"},
173+
Target: "available",
174+
Refresh: vpnConnectionRefreshFunc(conn, *vpnConnection.VPNConnectionID),
175+
Timeout: 30 * time.Minute,
176+
Delay: 10 * time.Second,
177+
MinTimeout: 10 * time.Second,
178+
}
179+
180+
_, stateErr := stateConf.WaitForState()
181+
if stateErr != nil {
182+
return fmt.Errorf(
183+
"Error waiting for VPN connection (%s) to become ready: %s",
184+
*vpnConnection.VPNConnectionID, err)
185+
}
186+
187+
// Create tags.
188+
if err := setTagsSDK(conn, d); err != nil {
189+
return err
190+
}
191+
192+
// Read off the API to populate our RO fields.
193+
return resourceAwsVpnConnectionRead(d, meta)
194+
}
195+
196+
func vpnConnectionRefreshFunc(conn *ec2.EC2, connectionId string) resource.StateRefreshFunc {
197+
return func() (interface{}, string, error) {
198+
resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{
199+
VPNConnectionIDs: []*string{aws.String(connectionId)},
200+
})
201+
202+
if err != nil {
203+
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
204+
resp = nil
205+
} else {
206+
log.Printf("Error on VPNConnectionRefresh: %s", err)
207+
return nil, "", err
208+
}
209+
}
210+
211+
if resp == nil || len(resp.VPNConnections) == 0 {
212+
return nil, "", nil
213+
}
214+
215+
connection := resp.VPNConnections[0]
216+
return connection, *connection.State, nil
217+
}
218+
}
219+
220+
func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) error {
221+
conn := meta.(*AWSClient).ec2conn
222+
223+
resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{
224+
VPNConnectionIDs: []*string{aws.String(d.Id())},
225+
})
226+
if err != nil {
227+
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
228+
d.SetId("")
229+
return nil
230+
} else {
231+
log.Printf("[ERROR] Error finding VPN connection: %s", err)
232+
return err
233+
}
234+
}
235+
236+
if len(resp.VPNConnections) != 1 {
237+
return fmt.Errorf("[ERROR] Error finding VPN connection: %s", d.Id())
238+
}
239+
240+
vpnConnection := resp.VPNConnections[0]
241+
242+
// Set attributes under the user's control.
243+
d.Set("vpn_gateway_id", vpnConnection.VPNGatewayID)
244+
d.Set("customer_gateway_id", vpnConnection.CustomerGatewayID)
245+
d.Set("type", vpnConnection.Type)
246+
d.Set("tags", tagsToMapSDK(vpnConnection.Tags))
247+
248+
if vpnConnection.Options != nil {
249+
if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil {
250+
return err
251+
}
252+
}
253+
254+
// Set read only attributes.
255+
d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration)
256+
if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VGWTelemetry)); err != nil {
257+
return err
258+
}
259+
if vpnConnection.Routes != nil {
260+
if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil {
261+
return err
262+
}
263+
}
264+
265+
return nil
266+
}
267+
268+
func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
269+
conn := meta.(*AWSClient).ec2conn
270+
271+
// Update tags if required.
272+
if err := setTagsSDK(conn, d); err != nil {
273+
return err
274+
}
275+
276+
d.SetPartial("tags")
277+
278+
return resourceAwsVpnConnectionRead(d, meta)
279+
}
280+
281+
func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error {
282+
conn := meta.(*AWSClient).ec2conn
283+
284+
_, err := conn.DeleteVPNConnection(&ec2.DeleteVPNConnectionInput{
285+
VPNConnectionID: aws.String(d.Id()),
286+
})
287+
if err != nil {
288+
if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
289+
d.SetId("")
290+
return nil
291+
} else {
292+
log.Printf("[ERROR] Error deleting VPN connection: %s", err)
293+
return err
294+
}
295+
}
296+
297+
// These things can take quite a while to tear themselves down and any
298+
// attempt to modify resources they reference (e.g. CustomerGateways or
299+
// VPN Gateways) before deletion will result in an error. Furthermore,
300+
// they don't just disappear. The go into "deleted" state. We need to
301+
// wait to ensure any other modifications the user might make to their
302+
// VPC stack can safely run.
303+
stateConf := &resource.StateChangeConf{
304+
Pending: []string{"deleting"},
305+
Target: "deleted",
306+
Refresh: vpnConnectionRefreshFunc(conn, d.Id()),
307+
Timeout: 30 * time.Minute,
308+
Delay: 10 * time.Second,
309+
MinTimeout: 10 * time.Second,
310+
}
311+
312+
_, stateErr := stateConf.WaitForState()
313+
if stateErr != nil {
314+
return fmt.Errorf(
315+
"Error waiting for VPN connection (%s) to delete: %s", d.Id(), err)
316+
}
317+
318+
return nil
319+
}
320+
321+
// routesToMapList turns the list of routes into a list of maps.
322+
func routesToMapList(routes []*ec2.VPNStaticRoute) []map[string]interface{} {
323+
result := make([]map[string]interface{}, 0, len(routes))
324+
for _, r := range routes {
325+
staticRoute := make(map[string]interface{})
326+
staticRoute["destination_cidr_block"] = *r.DestinationCIDRBlock
327+
staticRoute["state"] = *r.State
328+
329+
if r.Source != nil {
330+
staticRoute["source"] = *r.Source
331+
}
332+
333+
result = append(result, staticRoute)
334+
}
335+
336+
return result
337+
}
338+
339+
// telemetryToMapList turns the VGW telemetry into a list of maps.
340+
func telemetryToMapList(telemetry []*ec2.VGWTelemetry) []map[string]interface{} {
341+
result := make([]map[string]interface{}, 0, len(telemetry))
342+
for _, t := range telemetry {
343+
vgw := make(map[string]interface{})
344+
vgw["accepted_route_count"] = *t.AcceptedRouteCount
345+
vgw["outside_ip_address"] = *t.OutsideIPAddress
346+
vgw["status"] = *t.Status
347+
vgw["status_message"] = *t.StatusMessage
348+
349+
// LastStatusChange is a time.Time(). Convert it into a string
350+
// so it can be handled by schema's type system.
351+
vgw["last_status_change"] = t.LastStatusChange.String()
352+
result = append(result, vgw)
353+
}
354+
355+
return result
356+
}

0 commit comments

Comments
 (0)