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