github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/builtin/providers/aws/resource_aws_route_table.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 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsRouteTable() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsRouteTableCreate, 20 Read: resourceAwsRouteTableRead, 21 Update: resourceAwsRouteTableUpdate, 22 Delete: resourceAwsRouteTableDelete, 23 Importer: &schema.ResourceImporter{ 24 State: resourceAwsRouteTableImportState, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "vpc_id": { 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 }, 33 34 "tags": tagsSchema(), 35 36 "propagating_vgws": { 37 Type: schema.TypeSet, 38 Optional: true, 39 Computed: true, 40 Elem: &schema.Schema{Type: schema.TypeString}, 41 Set: schema.HashString, 42 }, 43 44 "route": { 45 Type: schema.TypeSet, 46 Computed: true, 47 Optional: true, 48 Elem: &schema.Resource{ 49 Schema: map[string]*schema.Schema{ 50 "cidr_block": { 51 Type: schema.TypeString, 52 Optional: true, 53 }, 54 55 "ipv6_cidr_block": { 56 Type: schema.TypeString, 57 Optional: true, 58 }, 59 60 "egress_only_gateway_id": { 61 Type: schema.TypeString, 62 Optional: true, 63 }, 64 65 "gateway_id": { 66 Type: schema.TypeString, 67 Optional: true, 68 }, 69 70 "instance_id": { 71 Type: schema.TypeString, 72 Optional: true, 73 }, 74 75 "nat_gateway_id": { 76 Type: schema.TypeString, 77 Optional: true, 78 }, 79 80 "vpc_peering_connection_id": { 81 Type: schema.TypeString, 82 Optional: true, 83 }, 84 85 "network_interface_id": { 86 Type: schema.TypeString, 87 Optional: true, 88 }, 89 }, 90 }, 91 Set: resourceAwsRouteTableHash, 92 }, 93 }, 94 } 95 } 96 97 func resourceAwsRouteTableCreate(d *schema.ResourceData, meta interface{}) error { 98 conn := meta.(*AWSClient).ec2conn 99 100 // Create the routing table 101 createOpts := &ec2.CreateRouteTableInput{ 102 VpcId: aws.String(d.Get("vpc_id").(string)), 103 } 104 log.Printf("[DEBUG] RouteTable create config: %#v", createOpts) 105 106 resp, err := conn.CreateRouteTable(createOpts) 107 if err != nil { 108 return fmt.Errorf("Error creating route table: %s", err) 109 } 110 111 // Get the ID and store it 112 rt := resp.RouteTable 113 d.SetId(*rt.RouteTableId) 114 log.Printf("[INFO] Route Table ID: %s", d.Id()) 115 116 // Wait for the route table to become available 117 log.Printf( 118 "[DEBUG] Waiting for route table (%s) to become available", 119 d.Id()) 120 stateConf := &resource.StateChangeConf{ 121 Pending: []string{"pending"}, 122 Target: []string{"ready"}, 123 Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()), 124 Timeout: 10 * time.Minute, 125 } 126 if _, err := stateConf.WaitForState(); err != nil { 127 return fmt.Errorf( 128 "Error waiting for route table (%s) to become available: %s", 129 d.Id(), err) 130 } 131 132 return resourceAwsRouteTableUpdate(d, meta) 133 } 134 135 func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { 136 conn := meta.(*AWSClient).ec2conn 137 138 rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())() 139 if err != nil { 140 return err 141 } 142 if rtRaw == nil { 143 d.SetId("") 144 return nil 145 } 146 147 rt := rtRaw.(*ec2.RouteTable) 148 d.Set("vpc_id", rt.VpcId) 149 150 propagatingVGWs := make([]string, 0, len(rt.PropagatingVgws)) 151 for _, vgw := range rt.PropagatingVgws { 152 propagatingVGWs = append(propagatingVGWs, *vgw.GatewayId) 153 } 154 d.Set("propagating_vgws", propagatingVGWs) 155 156 // Create an empty schema.Set to hold all routes 157 route := &schema.Set{F: resourceAwsRouteTableHash} 158 159 // Loop through the routes and add them to the set 160 for _, r := range rt.Routes { 161 if r.GatewayId != nil && *r.GatewayId == "local" { 162 continue 163 } 164 165 if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" { 166 continue 167 } 168 169 if r.DestinationPrefixListId != nil { 170 // Skipping because VPC endpoint routes are handled separately 171 // See aws_vpc_endpoint 172 continue 173 } 174 175 m := make(map[string]interface{}) 176 177 if r.DestinationCidrBlock != nil { 178 m["cidr_block"] = *r.DestinationCidrBlock 179 } 180 if r.DestinationIpv6CidrBlock != nil { 181 m["ipv6_cidr_block"] = *r.DestinationIpv6CidrBlock 182 } 183 if r.EgressOnlyInternetGatewayId != nil { 184 m["egress_only_gateway_id"] = *r.EgressOnlyInternetGatewayId 185 } 186 if r.GatewayId != nil { 187 m["gateway_id"] = *r.GatewayId 188 } 189 if r.NatGatewayId != nil { 190 m["nat_gateway_id"] = *r.NatGatewayId 191 } 192 if r.InstanceId != nil { 193 m["instance_id"] = *r.InstanceId 194 } 195 if r.VpcPeeringConnectionId != nil { 196 m["vpc_peering_connection_id"] = *r.VpcPeeringConnectionId 197 } 198 if r.NetworkInterfaceId != nil { 199 m["network_interface_id"] = *r.NetworkInterfaceId 200 } 201 202 route.Add(m) 203 } 204 d.Set("route", route) 205 206 // Tags 207 d.Set("tags", tagsToMap(rt.Tags)) 208 209 return nil 210 } 211 212 func resourceAwsRouteTableUpdate(d *schema.ResourceData, meta interface{}) error { 213 conn := meta.(*AWSClient).ec2conn 214 215 if d.HasChange("propagating_vgws") { 216 o, n := d.GetChange("propagating_vgws") 217 os := o.(*schema.Set) 218 ns := n.(*schema.Set) 219 remove := os.Difference(ns).List() 220 add := ns.Difference(os).List() 221 222 // Now first loop through all the old propagations and disable any obsolete ones 223 for _, vgw := range remove { 224 id := vgw.(string) 225 226 // Disable the propagation as it no longer exists in the config 227 log.Printf( 228 "[INFO] Deleting VGW propagation from %s: %s", 229 d.Id(), id) 230 _, err := conn.DisableVgwRoutePropagation(&ec2.DisableVgwRoutePropagationInput{ 231 RouteTableId: aws.String(d.Id()), 232 GatewayId: aws.String(id), 233 }) 234 if err != nil { 235 return err 236 } 237 } 238 239 // Make sure we save the state of the currently configured rules 240 propagatingVGWs := os.Intersection(ns) 241 d.Set("propagating_vgws", propagatingVGWs) 242 243 // Then loop through all the newly configured propagations and enable them 244 for _, vgw := range add { 245 id := vgw.(string) 246 247 var err error 248 for i := 0; i < 5; i++ { 249 log.Printf("[INFO] Enabling VGW propagation for %s: %s", d.Id(), id) 250 _, err = conn.EnableVgwRoutePropagation(&ec2.EnableVgwRoutePropagationInput{ 251 RouteTableId: aws.String(d.Id()), 252 GatewayId: aws.String(id), 253 }) 254 if err == nil { 255 break 256 } 257 258 // If we get a Gateway.NotAttached, it is usually some 259 // eventually consistency stuff. So we have to just wait a 260 // bit... 261 ec2err, ok := err.(awserr.Error) 262 if ok && ec2err.Code() == "Gateway.NotAttached" { 263 time.Sleep(20 * time.Second) 264 continue 265 } 266 } 267 if err != nil { 268 return err 269 } 270 271 propagatingVGWs.Add(vgw) 272 d.Set("propagating_vgws", propagatingVGWs) 273 } 274 } 275 276 // Check if the route set as a whole has changed 277 if d.HasChange("route") { 278 o, n := d.GetChange("route") 279 ors := o.(*schema.Set).Difference(n.(*schema.Set)) 280 nrs := n.(*schema.Set).Difference(o.(*schema.Set)) 281 282 // Now first loop through all the old routes and delete any obsolete ones 283 for _, route := range ors.List() { 284 m := route.(map[string]interface{}) 285 286 deleteOpts := &ec2.DeleteRouteInput{ 287 RouteTableId: aws.String(d.Id()), 288 } 289 290 if s := m["ipv6_cidr_block"].(string); s != "" { 291 deleteOpts.DestinationIpv6CidrBlock = aws.String(s) 292 293 log.Printf( 294 "[INFO] Deleting route from %s: %s", 295 d.Id(), m["ipv6_cidr_block"].(string)) 296 } 297 298 if s := m["cidr_block"].(string); s != "" { 299 deleteOpts.DestinationCidrBlock = aws.String(s) 300 301 log.Printf( 302 "[INFO] Deleting route from %s: %s", 303 d.Id(), m["cidr_block"].(string)) 304 } 305 306 _, err := conn.DeleteRoute(deleteOpts) 307 if err != nil { 308 return err 309 } 310 } 311 312 // Make sure we save the state of the currently configured rules 313 routes := o.(*schema.Set).Intersection(n.(*schema.Set)) 314 d.Set("route", routes) 315 316 // Then loop through all the newly configured routes and create them 317 for _, route := range nrs.List() { 318 m := route.(map[string]interface{}) 319 320 opts := ec2.CreateRouteInput{ 321 RouteTableId: aws.String(d.Id()), 322 } 323 324 if s := m["vpc_peering_connection_id"].(string); s != "" { 325 opts.VpcPeeringConnectionId = aws.String(s) 326 } 327 328 if s := m["network_interface_id"].(string); s != "" { 329 opts.NetworkInterfaceId = aws.String(s) 330 } 331 332 if s := m["instance_id"].(string); s != "" { 333 opts.InstanceId = aws.String(s) 334 } 335 336 if s := m["ipv6_cidr_block"].(string); s != "" { 337 opts.DestinationIpv6CidrBlock = aws.String(s) 338 } 339 340 if s := m["cidr_block"].(string); s != "" { 341 opts.DestinationCidrBlock = aws.String(s) 342 } 343 344 if s := m["gateway_id"].(string); s != "" { 345 opts.GatewayId = aws.String(s) 346 } 347 348 if s := m["egress_only_gateway_id"].(string); s != "" { 349 opts.EgressOnlyInternetGatewayId = aws.String(s) 350 } 351 352 if s := m["nat_gateway_id"].(string); s != "" { 353 opts.NatGatewayId = aws.String(s) 354 } 355 356 log.Printf("[INFO] Creating route for %s: %#v", d.Id(), opts) 357 err := resource.Retry(5*time.Minute, func() *resource.RetryError { 358 _, err := conn.CreateRoute(&opts) 359 if err != nil { 360 if awsErr, ok := err.(awserr.Error); ok { 361 if awsErr.Code() == "InvalidRouteTableID.NotFound" { 362 return resource.RetryableError(awsErr) 363 } 364 } 365 return resource.NonRetryableError(err) 366 } 367 return nil 368 }) 369 if err != nil { 370 return err 371 } 372 373 routes.Add(route) 374 d.Set("route", routes) 375 } 376 } 377 378 if err := setTags(conn, d); err != nil { 379 return err 380 } else { 381 d.SetPartial("tags") 382 } 383 384 return resourceAwsRouteTableRead(d, meta) 385 } 386 387 func resourceAwsRouteTableDelete(d *schema.ResourceData, meta interface{}) error { 388 conn := meta.(*AWSClient).ec2conn 389 390 // First request the routing table since we'll have to disassociate 391 // all the subnets first. 392 rtRaw, _, err := resourceAwsRouteTableStateRefreshFunc(conn, d.Id())() 393 if err != nil { 394 return err 395 } 396 if rtRaw == nil { 397 return nil 398 } 399 rt := rtRaw.(*ec2.RouteTable) 400 401 // Do all the disassociations 402 for _, a := range rt.Associations { 403 log.Printf("[INFO] Disassociating association: %s", *a.RouteTableAssociationId) 404 _, err := conn.DisassociateRouteTable(&ec2.DisassociateRouteTableInput{ 405 AssociationId: a.RouteTableAssociationId, 406 }) 407 if err != nil { 408 // First check if the association ID is not found. If this 409 // is the case, then it was already disassociated somehow, 410 // and that is okay. 411 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidAssociationID.NotFound" { 412 err = nil 413 } 414 } 415 if err != nil { 416 return err 417 } 418 } 419 420 // Delete the route table 421 log.Printf("[INFO] Deleting Route Table: %s", d.Id()) 422 _, err = conn.DeleteRouteTable(&ec2.DeleteRouteTableInput{ 423 RouteTableId: aws.String(d.Id()), 424 }) 425 if err != nil { 426 ec2err, ok := err.(awserr.Error) 427 if ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 428 return nil 429 } 430 431 return fmt.Errorf("Error deleting route table: %s", err) 432 } 433 434 // Wait for the route table to really destroy 435 log.Printf( 436 "[DEBUG] Waiting for route table (%s) to become destroyed", 437 d.Id()) 438 439 stateConf := &resource.StateChangeConf{ 440 Pending: []string{"ready"}, 441 Target: []string{}, 442 Refresh: resourceAwsRouteTableStateRefreshFunc(conn, d.Id()), 443 Timeout: 5 * time.Minute, 444 } 445 if _, err := stateConf.WaitForState(); err != nil { 446 return fmt.Errorf( 447 "Error waiting for route table (%s) to become destroyed: %s", 448 d.Id(), err) 449 } 450 451 return nil 452 } 453 454 func resourceAwsRouteTableHash(v interface{}) int { 455 var buf bytes.Buffer 456 m, castOk := v.(map[string]interface{}) 457 if !castOk { 458 return 0 459 } 460 461 if v, ok := m["ipv6_cidr_block"]; ok { 462 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 463 } 464 465 if v, ok := m["cidr_block"]; ok { 466 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 467 } 468 469 if v, ok := m["gateway_id"]; ok { 470 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 471 } 472 473 if v, ok := m["egress_only_gateway_id"]; ok { 474 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 475 } 476 477 natGatewaySet := false 478 if v, ok := m["nat_gateway_id"]; ok { 479 natGatewaySet = v.(string) != "" 480 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 481 } 482 483 instanceSet := false 484 if v, ok := m["instance_id"]; ok { 485 instanceSet = v.(string) != "" 486 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 487 } 488 489 if v, ok := m["vpc_peering_connection_id"]; ok { 490 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 491 } 492 493 if v, ok := m["network_interface_id"]; ok && !(instanceSet || natGatewaySet) { 494 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 495 } 496 497 return hashcode.String(buf.String()) 498 } 499 500 // resourceAwsRouteTableStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 501 // a RouteTable. 502 func resourceAwsRouteTableStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 503 return func() (interface{}, string, error) { 504 resp, err := conn.DescribeRouteTables(&ec2.DescribeRouteTablesInput{ 505 RouteTableIds: []*string{aws.String(id)}, 506 }) 507 if err != nil { 508 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 509 resp = nil 510 } else { 511 log.Printf("Error on RouteTableStateRefresh: %s", err) 512 return nil, "", err 513 } 514 } 515 516 if resp == nil { 517 // Sometimes AWS just has consistency issues and doesn't see 518 // our instance yet. Return an empty state. 519 return nil, "", nil 520 } 521 522 rt := resp.RouteTables[0] 523 return rt, "ready", nil 524 } 525 }