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