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