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