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