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