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