github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/aws/resource_aws_vpn_connection.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/awslabs/aws-sdk-go/aws" 10 "github.com/awslabs/aws-sdk-go/service/ec2" 11 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsVpnConnection() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsVpnConnectionCreate, 20 Read: resourceAwsVpnConnectionRead, 21 Update: resourceAwsVpnConnectionUpdate, 22 Delete: resourceAwsVpnConnectionDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "vpn_gateway_id": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 }, 30 31 "customer_gateway_id": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 ForceNew: true, 35 }, 36 37 "type": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 43 "static_routes_only": &schema.Schema{ 44 Type: schema.TypeBool, 45 Required: true, 46 ForceNew: true, 47 }, 48 49 "tags": tagsSchema(), 50 51 // Begin read only attributes 52 "customer_gateway_configuration": &schema.Schema{ 53 Type: schema.TypeString, 54 Computed: true, 55 Optional: true, 56 }, 57 58 "routes": &schema.Schema{ 59 Type: schema.TypeSet, 60 Computed: true, 61 Optional: true, 62 Elem: &schema.Resource{ 63 Schema: map[string]*schema.Schema{ 64 "destination_cidr_block": &schema.Schema{ 65 Type: schema.TypeString, 66 Computed: true, 67 Optional: true, 68 }, 69 70 "source": &schema.Schema{ 71 Type: schema.TypeString, 72 Computed: true, 73 Optional: true, 74 }, 75 76 "state": &schema.Schema{ 77 Type: schema.TypeString, 78 Computed: true, 79 Optional: true, 80 }, 81 }, 82 }, 83 Set: func(v interface{}) int { 84 var buf bytes.Buffer 85 m := v.(map[string]interface{}) 86 buf.WriteString(fmt.Sprintf("%s-", m["destination_cidr_block"].(string))) 87 buf.WriteString(fmt.Sprintf("%s-", m["source"].(string))) 88 buf.WriteString(fmt.Sprintf("%s-", m["state"].(string))) 89 return hashcode.String(buf.String()) 90 }, 91 }, 92 93 "vgw_telemetry": &schema.Schema{ 94 Type: schema.TypeSet, 95 Computed: true, 96 Optional: true, 97 Elem: &schema.Resource{ 98 Schema: map[string]*schema.Schema{ 99 "accepted_route_count": &schema.Schema{ 100 Type: schema.TypeInt, 101 Computed: true, 102 Optional: true, 103 }, 104 105 "last_status_change": &schema.Schema{ 106 Type: schema.TypeString, 107 Computed: true, 108 Optional: true, 109 }, 110 111 "outside_ip_address": &schema.Schema{ 112 Type: schema.TypeString, 113 Computed: true, 114 Optional: true, 115 }, 116 117 "status": &schema.Schema{ 118 Type: schema.TypeString, 119 Computed: true, 120 Optional: true, 121 }, 122 123 "status_message": &schema.Schema{ 124 Type: schema.TypeString, 125 Computed: true, 126 Optional: true, 127 }, 128 }, 129 }, 130 Set: func(v interface{}) int { 131 var buf bytes.Buffer 132 m := v.(map[string]interface{}) 133 buf.WriteString(fmt.Sprintf("%s-", m["outside_ip_address"].(string))) 134 return hashcode.String(buf.String()) 135 }, 136 }, 137 }, 138 } 139 } 140 141 func resourceAwsVpnConnectionCreate(d *schema.ResourceData, meta interface{}) error { 142 conn := meta.(*AWSClient).ec2conn 143 144 connectOpts := &ec2.VPNConnectionOptionsSpecification{ 145 StaticRoutesOnly: aws.Boolean(d.Get("static_routes_only").(bool)), 146 } 147 148 createOpts := &ec2.CreateVPNConnectionInput{ 149 CustomerGatewayID: aws.String(d.Get("customer_gateway_id").(string)), 150 Options: connectOpts, 151 Type: aws.String(d.Get("type").(string)), 152 VPNGatewayID: aws.String(d.Get("vpn_gateway_id").(string)), 153 } 154 155 // Create the VPN Connection 156 log.Printf("[DEBUG] Creating vpn connection") 157 resp, err := conn.CreateVPNConnection(createOpts) 158 if err != nil { 159 return fmt.Errorf("Error creating vpn connection: %s", err) 160 } 161 162 // Store the ID 163 vpnConnection := resp.VPNConnection 164 d.SetId(*vpnConnection.VPNConnectionID) 165 log.Printf("[INFO] VPN connection ID: %s", *vpnConnection.VPNConnectionID) 166 167 // Wait for the connection to become available. This has an obscenely 168 // high default timeout because AWS VPN connections are notoriously 169 // slow at coming up or going down. There's also no point in checking 170 // more frequently than every ten seconds. 171 stateConf := &resource.StateChangeConf{ 172 Pending: []string{"pending"}, 173 Target: "available", 174 Refresh: vpnConnectionRefreshFunc(conn, *vpnConnection.VPNConnectionID), 175 Timeout: 30 * time.Minute, 176 Delay: 10 * time.Second, 177 MinTimeout: 10 * time.Second, 178 } 179 180 _, stateErr := stateConf.WaitForState() 181 if stateErr != nil { 182 return fmt.Errorf( 183 "Error waiting for VPN connection (%s) to become ready: %s", 184 *vpnConnection.VPNConnectionID, err) 185 } 186 187 // Create tags. 188 if err := setTagsSDK(conn, d); err != nil { 189 return err 190 } 191 192 // Read off the API to populate our RO fields. 193 return resourceAwsVpnConnectionRead(d, meta) 194 } 195 196 func vpnConnectionRefreshFunc(conn *ec2.EC2, connectionId string) resource.StateRefreshFunc { 197 return func() (interface{}, string, error) { 198 resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{ 199 VPNConnectionIDs: []*string{aws.String(connectionId)}, 200 }) 201 202 if err != nil { 203 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" { 204 resp = nil 205 } else { 206 log.Printf("Error on VPNConnectionRefresh: %s", err) 207 return nil, "", err 208 } 209 } 210 211 if resp == nil || len(resp.VPNConnections) == 0 { 212 return nil, "", nil 213 } 214 215 connection := resp.VPNConnections[0] 216 return connection, *connection.State, nil 217 } 218 } 219 220 func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) error { 221 conn := meta.(*AWSClient).ec2conn 222 223 resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{ 224 VPNConnectionIDs: []*string{aws.String(d.Id())}, 225 }) 226 if err != nil { 227 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" { 228 d.SetId("") 229 return nil 230 } else { 231 log.Printf("[ERROR] Error finding VPN connection: %s", err) 232 return err 233 } 234 } 235 236 if len(resp.VPNConnections) != 1 { 237 return fmt.Errorf("[ERROR] Error finding VPN connection: %s", d.Id()) 238 } 239 240 vpnConnection := resp.VPNConnections[0] 241 242 // Set attributes under the user's control. 243 d.Set("vpn_gateway_id", vpnConnection.VPNGatewayID) 244 d.Set("customer_gateway_id", vpnConnection.CustomerGatewayID) 245 d.Set("type", vpnConnection.Type) 246 d.Set("tags", tagsToMapSDK(vpnConnection.Tags)) 247 248 if vpnConnection.Options != nil { 249 if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil { 250 return err 251 } 252 } 253 254 // Set read only attributes. 255 d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration) 256 if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VGWTelemetry)); err != nil { 257 return err 258 } 259 if vpnConnection.Routes != nil { 260 if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil { 261 return err 262 } 263 } 264 265 return nil 266 } 267 268 func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error { 269 conn := meta.(*AWSClient).ec2conn 270 271 // Update tags if required. 272 if err := setTagsSDK(conn, d); err != nil { 273 return err 274 } 275 276 d.SetPartial("tags") 277 278 return resourceAwsVpnConnectionRead(d, meta) 279 } 280 281 func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error { 282 conn := meta.(*AWSClient).ec2conn 283 284 _, err := conn.DeleteVPNConnection(&ec2.DeleteVPNConnectionInput{ 285 VPNConnectionID: aws.String(d.Id()), 286 }) 287 if err != nil { 288 if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" { 289 d.SetId("") 290 return nil 291 } else { 292 log.Printf("[ERROR] Error deleting VPN connection: %s", err) 293 return err 294 } 295 } 296 297 // These things can take quite a while to tear themselves down and any 298 // attempt to modify resources they reference (e.g. CustomerGateways or 299 // VPN Gateways) before deletion will result in an error. Furthermore, 300 // they don't just disappear. The go into "deleted" state. We need to 301 // wait to ensure any other modifications the user might make to their 302 // VPC stack can safely run. 303 stateConf := &resource.StateChangeConf{ 304 Pending: []string{"deleting"}, 305 Target: "deleted", 306 Refresh: vpnConnectionRefreshFunc(conn, d.Id()), 307 Timeout: 30 * time.Minute, 308 Delay: 10 * time.Second, 309 MinTimeout: 10 * time.Second, 310 } 311 312 _, stateErr := stateConf.WaitForState() 313 if stateErr != nil { 314 return fmt.Errorf( 315 "Error waiting for VPN connection (%s) to delete: %s", d.Id(), err) 316 } 317 318 return nil 319 } 320 321 // routesToMapList turns the list of routes into a list of maps. 322 func routesToMapList(routes []*ec2.VPNStaticRoute) []map[string]interface{} { 323 result := make([]map[string]interface{}, 0, len(routes)) 324 for _, r := range routes { 325 staticRoute := make(map[string]interface{}) 326 staticRoute["destination_cidr_block"] = *r.DestinationCIDRBlock 327 staticRoute["state"] = *r.State 328 329 if r.Source != nil { 330 staticRoute["source"] = *r.Source 331 } 332 333 result = append(result, staticRoute) 334 } 335 336 return result 337 } 338 339 // telemetryToMapList turns the VGW telemetry into a list of maps. 340 func telemetryToMapList(telemetry []*ec2.VGWTelemetry) []map[string]interface{} { 341 result := make([]map[string]interface{}, 0, len(telemetry)) 342 for _, t := range telemetry { 343 vgw := make(map[string]interface{}) 344 vgw["accepted_route_count"] = *t.AcceptedRouteCount 345 vgw["outside_ip_address"] = *t.OutsideIPAddress 346 vgw["status"] = *t.Status 347 vgw["status_message"] = *t.StatusMessage 348 349 // LastStatusChange is a time.Time(). Convert it into a string 350 // so it can be handled by schema's type system. 351 vgw["last_status_change"] = t.LastStatusChange.String() 352 result = append(result, vgw) 353 } 354 355 return result 356 }