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