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