github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_vpn_connection.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "encoding/xml" 6 "fmt" 7 "log" 8 "sort" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/awserr" 13 "github.com/aws/aws-sdk-go/service/ec2" 14 15 "github.com/hashicorp/errwrap" 16 "github.com/hashicorp/terraform/helper/hashcode" 17 "github.com/hashicorp/terraform/helper/resource" 18 "github.com/hashicorp/terraform/helper/schema" 19 ) 20 21 type XmlVpnConnectionConfig struct { 22 Tunnels []XmlIpsecTunnel `xml:"ipsec_tunnel"` 23 } 24 25 type XmlIpsecTunnel struct { 26 OutsideAddress string `xml:"vpn_gateway>tunnel_outside_address>ip_address"` 27 PreSharedKey string `xml:"ike>pre_shared_key"` 28 } 29 30 type TunnelInfo struct { 31 Tunnel1Address string 32 Tunnel1PreSharedKey string 33 Tunnel2Address string 34 Tunnel2PreSharedKey string 35 } 36 37 func (slice XmlVpnConnectionConfig) Len() int { 38 return len(slice.Tunnels) 39 } 40 41 func (slice XmlVpnConnectionConfig) Less(i, j int) bool { 42 return slice.Tunnels[i].OutsideAddress < slice.Tunnels[j].OutsideAddress 43 } 44 45 func (slice XmlVpnConnectionConfig) Swap(i, j int) { 46 slice.Tunnels[i], slice.Tunnels[j] = slice.Tunnels[j], slice.Tunnels[i] 47 } 48 49 func resourceAwsVpnConnection() *schema.Resource { 50 return &schema.Resource{ 51 Create: resourceAwsVpnConnectionCreate, 52 Read: resourceAwsVpnConnectionRead, 53 Update: resourceAwsVpnConnectionUpdate, 54 Delete: resourceAwsVpnConnectionDelete, 55 Importer: &schema.ResourceImporter{ 56 State: schema.ImportStatePassthrough, 57 }, 58 59 Schema: map[string]*schema.Schema{ 60 "vpn_gateway_id": { 61 Type: schema.TypeString, 62 Required: true, 63 ForceNew: true, 64 }, 65 66 "customer_gateway_id": { 67 Type: schema.TypeString, 68 Required: true, 69 ForceNew: true, 70 }, 71 72 "type": { 73 Type: schema.TypeString, 74 Required: true, 75 ForceNew: true, 76 }, 77 78 "static_routes_only": { 79 Type: schema.TypeBool, 80 Optional: true, 81 Computed: true, 82 ForceNew: true, 83 }, 84 85 "tags": tagsSchema(), 86 87 // Begin read only attributes 88 "customer_gateway_configuration": { 89 Type: schema.TypeString, 90 Computed: true, 91 Optional: true, 92 }, 93 94 "tunnel1_address": { 95 Type: schema.TypeString, 96 Computed: true, 97 }, 98 99 "tunnel1_preshared_key": { 100 Type: schema.TypeString, 101 Computed: true, 102 }, 103 104 "tunnel2_address": { 105 Type: schema.TypeString, 106 Computed: true, 107 }, 108 109 "tunnel2_preshared_key": { 110 Type: schema.TypeString, 111 Computed: true, 112 }, 113 114 "routes": { 115 Type: schema.TypeSet, 116 Computed: true, 117 Optional: true, 118 Elem: &schema.Resource{ 119 Schema: map[string]*schema.Schema{ 120 "destination_cidr_block": { 121 Type: schema.TypeString, 122 Computed: true, 123 Optional: true, 124 }, 125 126 "source": { 127 Type: schema.TypeString, 128 Computed: true, 129 Optional: true, 130 }, 131 132 "state": { 133 Type: schema.TypeString, 134 Computed: true, 135 Optional: true, 136 }, 137 }, 138 }, 139 Set: func(v interface{}) int { 140 var buf bytes.Buffer 141 m := v.(map[string]interface{}) 142 buf.WriteString(fmt.Sprintf("%s-", m["destination_cidr_block"].(string))) 143 buf.WriteString(fmt.Sprintf("%s-", m["source"].(string))) 144 buf.WriteString(fmt.Sprintf("%s-", m["state"].(string))) 145 return hashcode.String(buf.String()) 146 }, 147 }, 148 149 "vgw_telemetry": { 150 Type: schema.TypeSet, 151 Computed: true, 152 Optional: true, 153 Elem: &schema.Resource{ 154 Schema: map[string]*schema.Schema{ 155 "accepted_route_count": { 156 Type: schema.TypeInt, 157 Computed: true, 158 Optional: true, 159 }, 160 161 "last_status_change": { 162 Type: schema.TypeString, 163 Computed: true, 164 Optional: true, 165 }, 166 167 "outside_ip_address": { 168 Type: schema.TypeString, 169 Computed: true, 170 Optional: true, 171 }, 172 173 "status": { 174 Type: schema.TypeString, 175 Computed: true, 176 Optional: true, 177 }, 178 179 "status_message": { 180 Type: schema.TypeString, 181 Computed: true, 182 Optional: true, 183 }, 184 }, 185 }, 186 Set: func(v interface{}) int { 187 var buf bytes.Buffer 188 m := v.(map[string]interface{}) 189 buf.WriteString(fmt.Sprintf("%s-", m["outside_ip_address"].(string))) 190 return hashcode.String(buf.String()) 191 }, 192 }, 193 }, 194 } 195 } 196 197 func resourceAwsVpnConnectionCreate(d *schema.ResourceData, meta interface{}) error { 198 conn := meta.(*AWSClient).ec2conn 199 200 connectOpts := &ec2.VpnConnectionOptionsSpecification{ 201 StaticRoutesOnly: aws.Bool(d.Get("static_routes_only").(bool)), 202 } 203 204 createOpts := &ec2.CreateVpnConnectionInput{ 205 CustomerGatewayId: aws.String(d.Get("customer_gateway_id").(string)), 206 Options: connectOpts, 207 Type: aws.String(d.Get("type").(string)), 208 VpnGatewayId: aws.String(d.Get("vpn_gateway_id").(string)), 209 } 210 211 // Create the VPN Connection 212 log.Printf("[DEBUG] Creating vpn connection") 213 resp, err := conn.CreateVpnConnection(createOpts) 214 if err != nil { 215 return fmt.Errorf("Error creating vpn connection: %s", err) 216 } 217 218 // Store the ID 219 vpnConnection := resp.VpnConnection 220 d.SetId(*vpnConnection.VpnConnectionId) 221 log.Printf("[INFO] VPN connection ID: %s", *vpnConnection.VpnConnectionId) 222 223 // Wait for the connection to become available. This has an obscenely 224 // high default timeout because AWS VPN connections are notoriously 225 // slow at coming up or going down. There's also no point in checking 226 // more frequently than every ten seconds. 227 stateConf := &resource.StateChangeConf{ 228 Pending: []string{"pending"}, 229 Target: []string{"available"}, 230 Refresh: vpnConnectionRefreshFunc(conn, *vpnConnection.VpnConnectionId), 231 Timeout: 30 * time.Minute, 232 Delay: 10 * time.Second, 233 MinTimeout: 10 * time.Second, 234 } 235 236 _, stateErr := stateConf.WaitForState() 237 if stateErr != nil { 238 return fmt.Errorf( 239 "Error waiting for VPN connection (%s) to become ready: %s", 240 *vpnConnection.VpnConnectionId, err) 241 } 242 243 // Create tags. 244 if err := setTags(conn, d); err != nil { 245 return err 246 } 247 248 // Read off the API to populate our RO fields. 249 return resourceAwsVpnConnectionRead(d, meta) 250 } 251 252 func vpnConnectionRefreshFunc(conn *ec2.EC2, connectionId string) resource.StateRefreshFunc { 253 return func() (interface{}, string, error) { 254 resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{ 255 VpnConnectionIds: []*string{aws.String(connectionId)}, 256 }) 257 258 if err != nil { 259 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { 260 resp = nil 261 } else { 262 log.Printf("Error on VPNConnectionRefresh: %s", err) 263 return nil, "", err 264 } 265 } 266 267 if resp == nil || len(resp.VpnConnections) == 0 { 268 return nil, "", nil 269 } 270 271 connection := resp.VpnConnections[0] 272 return connection, *connection.State, nil 273 } 274 } 275 276 func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) error { 277 conn := meta.(*AWSClient).ec2conn 278 279 resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{ 280 VpnConnectionIds: []*string{aws.String(d.Id())}, 281 }) 282 if err != nil { 283 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { 284 d.SetId("") 285 return nil 286 } else { 287 log.Printf("[ERROR] Error finding VPN connection: %s", err) 288 return err 289 } 290 } 291 292 if len(resp.VpnConnections) != 1 { 293 return fmt.Errorf("[ERROR] Error finding VPN connection: %s", d.Id()) 294 } 295 296 vpnConnection := resp.VpnConnections[0] 297 if vpnConnection == nil || *vpnConnection.State == "deleted" { 298 // Seems we have lost our VPN Connection 299 d.SetId("") 300 return nil 301 } 302 303 // Set attributes under the user's control. 304 d.Set("vpn_gateway_id", vpnConnection.VpnGatewayId) 305 d.Set("customer_gateway_id", vpnConnection.CustomerGatewayId) 306 d.Set("type", vpnConnection.Type) 307 d.Set("tags", tagsToMap(vpnConnection.Tags)) 308 309 if vpnConnection.Options != nil { 310 if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil { 311 return err 312 } 313 } else { 314 //If there no Options on the connection then we do not support *static_routes* 315 d.Set("static_routes_only", false) 316 } 317 318 // Set read only attributes. 319 d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration) 320 321 if vpnConnection.CustomerGatewayConfiguration != nil { 322 if tunnelInfo, err := xmlConfigToTunnelInfo(*vpnConnection.CustomerGatewayConfiguration); err != nil { 323 log.Printf("[ERR] Error unmarshaling XML configuration for (%s): %s", d.Id(), err) 324 } else { 325 d.Set("tunnel1_address", tunnelInfo.Tunnel1Address) 326 d.Set("tunnel1_preshared_key", tunnelInfo.Tunnel1PreSharedKey) 327 d.Set("tunnel2_address", tunnelInfo.Tunnel2Address) 328 d.Set("tunnel2_preshared_key", tunnelInfo.Tunnel2PreSharedKey) 329 } 330 } 331 332 if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VgwTelemetry)); err != nil { 333 return err 334 } 335 if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil { 336 return err 337 } 338 339 return nil 340 } 341 342 func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error { 343 conn := meta.(*AWSClient).ec2conn 344 345 // Update tags if required. 346 if err := setTags(conn, d); err != nil { 347 return err 348 } 349 350 d.SetPartial("tags") 351 352 return resourceAwsVpnConnectionRead(d, meta) 353 } 354 355 func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error { 356 conn := meta.(*AWSClient).ec2conn 357 358 _, err := conn.DeleteVpnConnection(&ec2.DeleteVpnConnectionInput{ 359 VpnConnectionId: aws.String(d.Id()), 360 }) 361 if err != nil { 362 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { 363 d.SetId("") 364 return nil 365 } else { 366 log.Printf("[ERROR] Error deleting VPN connection: %s", err) 367 return err 368 } 369 } 370 371 // These things can take quite a while to tear themselves down and any 372 // attempt to modify resources they reference (e.g. CustomerGateways or 373 // VPN Gateways) before deletion will result in an error. Furthermore, 374 // they don't just disappear. The go into "deleted" state. We need to 375 // wait to ensure any other modifications the user might make to their 376 // VPC stack can safely run. 377 stateConf := &resource.StateChangeConf{ 378 Pending: []string{"deleting"}, 379 Target: []string{"deleted"}, 380 Refresh: vpnConnectionRefreshFunc(conn, d.Id()), 381 Timeout: 30 * time.Minute, 382 Delay: 10 * time.Second, 383 MinTimeout: 10 * time.Second, 384 } 385 386 _, stateErr := stateConf.WaitForState() 387 if stateErr != nil { 388 return fmt.Errorf( 389 "Error waiting for VPN connection (%s) to delete: %s", d.Id(), err) 390 } 391 392 return nil 393 } 394 395 // routesToMapList turns the list of routes into a list of maps. 396 func routesToMapList(routes []*ec2.VpnStaticRoute) []map[string]interface{} { 397 result := make([]map[string]interface{}, 0, len(routes)) 398 for _, r := range routes { 399 staticRoute := make(map[string]interface{}) 400 staticRoute["destination_cidr_block"] = *r.DestinationCidrBlock 401 staticRoute["state"] = *r.State 402 403 if r.Source != nil { 404 staticRoute["source"] = *r.Source 405 } 406 407 result = append(result, staticRoute) 408 } 409 410 return result 411 } 412 413 // telemetryToMapList turns the VGW telemetry into a list of maps. 414 func telemetryToMapList(telemetry []*ec2.VgwTelemetry) []map[string]interface{} { 415 result := make([]map[string]interface{}, 0, len(telemetry)) 416 for _, t := range telemetry { 417 vgw := make(map[string]interface{}) 418 vgw["accepted_route_count"] = *t.AcceptedRouteCount 419 vgw["outside_ip_address"] = *t.OutsideIpAddress 420 vgw["status"] = *t.Status 421 vgw["status_message"] = *t.StatusMessage 422 423 // LastStatusChange is a time.Time(). Convert it into a string 424 // so it can be handled by schema's type system. 425 vgw["last_status_change"] = t.LastStatusChange.String() 426 result = append(result, vgw) 427 } 428 429 return result 430 } 431 432 func xmlConfigToTunnelInfo(xmlConfig string) (*TunnelInfo, error) { 433 var vpnConfig XmlVpnConnectionConfig 434 if err := xml.Unmarshal([]byte(xmlConfig), &vpnConfig); err != nil { 435 return nil, errwrap.Wrapf("Error Unmarshalling XML: {{err}}", err) 436 } 437 438 // don't expect consistent ordering from the XML 439 sort.Sort(vpnConfig) 440 441 tunnelInfo := TunnelInfo{ 442 Tunnel1Address: vpnConfig.Tunnels[0].OutsideAddress, 443 Tunnel1PreSharedKey: vpnConfig.Tunnels[0].PreSharedKey, 444 445 Tunnel2Address: vpnConfig.Tunnels[1].OutsideAddress, 446 Tunnel2PreSharedKey: vpnConfig.Tunnels[1].PreSharedKey, 447 } 448 449 return &tunnelInfo, nil 450 }