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