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