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