github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/aws/resource_aws_route.go (about) 1 package aws 2 3 import ( 4 "errors" 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 // How long to sleep if a limit-exceeded event happens 18 var routeTargetValidationError = errors.New("Error: more than 1 target specified. Only 1 of gateway_id, " + 19 "egress_only_gateway_id, nat_gateway_id, instance_id, network_interface_id or " + 20 "vpc_peering_connection_id is allowed.") 21 22 // AWS Route resource Schema declaration 23 func resourceAwsRoute() *schema.Resource { 24 return &schema.Resource{ 25 Create: resourceAwsRouteCreate, 26 Read: resourceAwsRouteRead, 27 Update: resourceAwsRouteUpdate, 28 Delete: resourceAwsRouteDelete, 29 Exists: resourceAwsRouteExists, 30 31 Schema: map[string]*schema.Schema{ 32 "destination_cidr_block": { 33 Type: schema.TypeString, 34 Optional: true, 35 ForceNew: true, 36 }, 37 "destination_ipv6_cidr_block": { 38 Type: schema.TypeString, 39 Optional: true, 40 ForceNew: true, 41 }, 42 43 "destination_prefix_list_id": { 44 Type: schema.TypeString, 45 Computed: true, 46 }, 47 48 "gateway_id": { 49 Type: schema.TypeString, 50 Optional: true, 51 Computed: true, 52 }, 53 54 "egress_only_gateway_id": { 55 Type: schema.TypeString, 56 Optional: true, 57 Computed: true, 58 }, 59 60 "nat_gateway_id": { 61 Type: schema.TypeString, 62 Optional: true, 63 Computed: true, 64 }, 65 66 "instance_id": { 67 Type: schema.TypeString, 68 Optional: true, 69 Computed: true, 70 }, 71 72 "instance_owner_id": { 73 Type: schema.TypeString, 74 Computed: true, 75 }, 76 77 "network_interface_id": { 78 Type: schema.TypeString, 79 Optional: true, 80 Computed: true, 81 }, 82 83 "origin": { 84 Type: schema.TypeString, 85 Computed: true, 86 }, 87 88 "state": { 89 Type: schema.TypeString, 90 Computed: true, 91 }, 92 93 "route_table_id": { 94 Type: schema.TypeString, 95 Required: true, 96 }, 97 98 "vpc_peering_connection_id": { 99 Type: schema.TypeString, 100 Optional: true, 101 }, 102 }, 103 } 104 } 105 106 func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { 107 conn := meta.(*AWSClient).ec2conn 108 var numTargets int 109 var setTarget string 110 allowedTargets := []string{ 111 "egress_only_gateway_id", 112 "gateway_id", 113 "nat_gateway_id", 114 "instance_id", 115 "network_interface_id", 116 "vpc_peering_connection_id", 117 } 118 119 // Check if more than 1 target is specified 120 for _, target := range allowedTargets { 121 if len(d.Get(target).(string)) > 0 { 122 numTargets++ 123 setTarget = target 124 } 125 } 126 127 if numTargets > 1 { 128 return routeTargetValidationError 129 } 130 131 createOpts := &ec2.CreateRouteInput{} 132 // Formulate CreateRouteInput based on the target type 133 switch setTarget { 134 case "gateway_id": 135 createOpts = &ec2.CreateRouteInput{ 136 RouteTableId: aws.String(d.Get("route_table_id").(string)), 137 GatewayId: aws.String(d.Get("gateway_id").(string)), 138 } 139 140 if v, ok := d.GetOk("destination_cidr_block"); ok { 141 createOpts.DestinationCidrBlock = aws.String(v.(string)) 142 } 143 144 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 145 createOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) 146 } 147 148 case "egress_only_gateway_id": 149 createOpts = &ec2.CreateRouteInput{ 150 RouteTableId: aws.String(d.Get("route_table_id").(string)), 151 DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), 152 EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), 153 } 154 case "nat_gateway_id": 155 createOpts = &ec2.CreateRouteInput{ 156 RouteTableId: aws.String(d.Get("route_table_id").(string)), 157 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 158 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 159 } 160 case "instance_id": 161 createOpts = &ec2.CreateRouteInput{ 162 RouteTableId: aws.String(d.Get("route_table_id").(string)), 163 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 164 InstanceId: aws.String(d.Get("instance_id").(string)), 165 } 166 case "network_interface_id": 167 createOpts = &ec2.CreateRouteInput{ 168 RouteTableId: aws.String(d.Get("route_table_id").(string)), 169 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 170 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 171 } 172 case "vpc_peering_connection_id": 173 createOpts = &ec2.CreateRouteInput{ 174 RouteTableId: aws.String(d.Get("route_table_id").(string)), 175 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 176 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 177 } 178 default: 179 return fmt.Errorf("An invalid target type specified: %s", setTarget) 180 } 181 log.Printf("[DEBUG] Route create config: %s", createOpts) 182 183 // Create the route 184 var err error 185 186 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 187 _, err = conn.CreateRoute(createOpts) 188 189 if err != nil { 190 ec2err, ok := err.(awserr.Error) 191 if !ok { 192 return resource.NonRetryableError(err) 193 } 194 if ec2err.Code() == "InvalidParameterException" { 195 log.Printf("[DEBUG] Trying to create route again: %q", ec2err.Message()) 196 return resource.RetryableError(err) 197 } 198 199 return resource.NonRetryableError(err) 200 } 201 202 return nil 203 }) 204 if err != nil { 205 return fmt.Errorf("Error creating route: %s", err) 206 } 207 208 var route *ec2.Route 209 210 if v, ok := d.GetOk("destination_cidr_block"); ok { 211 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 212 route, err = findResourceRoute(conn, d.Get("route_table_id").(string), v.(string), "") 213 return resource.RetryableError(err) 214 }) 215 if err != nil { 216 return fmt.Errorf("Error finding route after creating it: %s", err) 217 } 218 } 219 220 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 221 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 222 route, err = findResourceRoute(conn, d.Get("route_table_id").(string), "", v.(string)) 223 return resource.RetryableError(err) 224 }) 225 if err != nil { 226 return fmt.Errorf("Error finding route after creating it: %s", err) 227 } 228 } 229 230 d.SetId(routeIDHash(d, route)) 231 resourceAwsRouteSetResourceData(d, route) 232 return nil 233 } 234 235 func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { 236 conn := meta.(*AWSClient).ec2conn 237 routeTableId := d.Get("route_table_id").(string) 238 239 destinationCidrBlock := d.Get("destination_cidr_block").(string) 240 destinationIpv6CidrBlock := d.Get("destination_ipv6_cidr_block").(string) 241 242 route, err := findResourceRoute(conn, routeTableId, destinationCidrBlock, destinationIpv6CidrBlock) 243 if err != nil { 244 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 245 log.Printf("[WARN] Route Table %q could not be found. Removing Route from state.", 246 routeTableId) 247 d.SetId("") 248 return nil 249 } 250 return err 251 } 252 resourceAwsRouteSetResourceData(d, route) 253 return nil 254 } 255 256 func resourceAwsRouteSetResourceData(d *schema.ResourceData, route *ec2.Route) { 257 d.Set("destination_prefix_list_id", route.DestinationPrefixListId) 258 d.Set("gateway_id", route.GatewayId) 259 d.Set("egress_only_gateway_id", route.EgressOnlyInternetGatewayId) 260 d.Set("nat_gateway_id", route.NatGatewayId) 261 d.Set("instance_id", route.InstanceId) 262 d.Set("instance_owner_id", route.InstanceOwnerId) 263 d.Set("network_interface_id", route.NetworkInterfaceId) 264 d.Set("origin", route.Origin) 265 d.Set("state", route.State) 266 d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId) 267 } 268 269 func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { 270 conn := meta.(*AWSClient).ec2conn 271 var numTargets int 272 var setTarget string 273 274 allowedTargets := []string{ 275 "egress_only_gateway_id", 276 "gateway_id", 277 "nat_gateway_id", 278 "network_interface_id", 279 "instance_id", 280 "vpc_peering_connection_id", 281 } 282 replaceOpts := &ec2.ReplaceRouteInput{} 283 284 // Check if more than 1 target is specified 285 for _, target := range allowedTargets { 286 if len(d.Get(target).(string)) > 0 { 287 numTargets++ 288 setTarget = target 289 } 290 } 291 292 switch setTarget { 293 //instance_id is a special case due to the fact that AWS will "discover" the network_interace_id 294 //when it creates the route and return that data. In the case of an update, we should ignore the 295 //existing network_interface_id 296 case "instance_id": 297 if numTargets > 2 || (numTargets == 2 && len(d.Get("network_interface_id").(string)) == 0) { 298 return routeTargetValidationError 299 } 300 default: 301 if numTargets > 1 { 302 return routeTargetValidationError 303 } 304 } 305 306 // Formulate ReplaceRouteInput based on the target type 307 switch setTarget { 308 case "gateway_id": 309 replaceOpts = &ec2.ReplaceRouteInput{ 310 RouteTableId: aws.String(d.Get("route_table_id").(string)), 311 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 312 GatewayId: aws.String(d.Get("gateway_id").(string)), 313 } 314 case "egress_only_gateway_id": 315 replaceOpts = &ec2.ReplaceRouteInput{ 316 RouteTableId: aws.String(d.Get("route_table_id").(string)), 317 DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), 318 EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), 319 } 320 case "nat_gateway_id": 321 replaceOpts = &ec2.ReplaceRouteInput{ 322 RouteTableId: aws.String(d.Get("route_table_id").(string)), 323 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 324 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 325 } 326 case "instance_id": 327 replaceOpts = &ec2.ReplaceRouteInput{ 328 RouteTableId: aws.String(d.Get("route_table_id").(string)), 329 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 330 InstanceId: aws.String(d.Get("instance_id").(string)), 331 } 332 case "network_interface_id": 333 replaceOpts = &ec2.ReplaceRouteInput{ 334 RouteTableId: aws.String(d.Get("route_table_id").(string)), 335 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 336 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 337 } 338 case "vpc_peering_connection_id": 339 replaceOpts = &ec2.ReplaceRouteInput{ 340 RouteTableId: aws.String(d.Get("route_table_id").(string)), 341 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 342 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 343 } 344 default: 345 return fmt.Errorf("An invalid target type specified: %s", setTarget) 346 } 347 log.Printf("[DEBUG] Route replace config: %s", replaceOpts) 348 349 // Replace the route 350 _, err := conn.ReplaceRoute(replaceOpts) 351 if err != nil { 352 return err 353 } 354 355 return nil 356 } 357 358 func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { 359 conn := meta.(*AWSClient).ec2conn 360 361 deleteOpts := &ec2.DeleteRouteInput{ 362 RouteTableId: aws.String(d.Get("route_table_id").(string)), 363 } 364 if v, ok := d.GetOk("destination_cidr_block"); ok { 365 deleteOpts.DestinationCidrBlock = aws.String(v.(string)) 366 } 367 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 368 deleteOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) 369 } 370 log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) 371 372 var err error 373 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 374 log.Printf("[DEBUG] Trying to delete route with opts %s", deleteOpts) 375 resp, err := conn.DeleteRoute(deleteOpts) 376 log.Printf("[DEBUG] Route delete result: %s", resp) 377 378 if err == nil { 379 return nil 380 } 381 382 ec2err, ok := err.(awserr.Error) 383 if !ok { 384 return resource.NonRetryableError(err) 385 } 386 if ec2err.Code() == "InvalidParameterException" { 387 log.Printf("[DEBUG] Trying to delete route again: %q", 388 ec2err.Message()) 389 return resource.RetryableError(err) 390 } 391 392 return resource.NonRetryableError(err) 393 }) 394 395 if err != nil { 396 return err 397 } 398 399 d.SetId("") 400 return nil 401 } 402 403 func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, error) { 404 conn := meta.(*AWSClient).ec2conn 405 routeTableId := d.Get("route_table_id").(string) 406 407 findOpts := &ec2.DescribeRouteTablesInput{ 408 RouteTableIds: []*string{&routeTableId}, 409 } 410 411 res, err := conn.DescribeRouteTables(findOpts) 412 if err != nil { 413 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 414 log.Printf("[WARN] Route Table %q could not be found.", routeTableId) 415 return false, nil 416 } 417 return false, fmt.Errorf("Error while checking if route exists: %s", err) 418 } 419 420 if len(res.RouteTables) < 1 || res.RouteTables[0] == nil { 421 log.Printf("[WARN] Route Table %q is gone, or route does not exist.", 422 routeTableId) 423 return false, nil 424 } 425 426 if v, ok := d.GetOk("destination_cidr_block"); ok { 427 for _, route := range (*res.RouteTables[0]).Routes { 428 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == v.(string) { 429 return true, nil 430 } 431 } 432 } 433 434 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 435 for _, route := range (*res.RouteTables[0]).Routes { 436 if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == v.(string) { 437 return true, nil 438 } 439 } 440 } 441 442 return false, nil 443 } 444 445 // Create an ID for a route 446 func routeIDHash(d *schema.ResourceData, r *ec2.Route) string { 447 448 if r.DestinationIpv6CidrBlock != nil && *r.DestinationIpv6CidrBlock != "" { 449 return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationIpv6CidrBlock)) 450 } 451 452 return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) 453 } 454 455 // Helper: retrieve a route 456 func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string, ipv6cidr string) (*ec2.Route, error) { 457 routeTableID := rtbid 458 459 findOpts := &ec2.DescribeRouteTablesInput{ 460 RouteTableIds: []*string{&routeTableID}, 461 } 462 463 resp, err := conn.DescribeRouteTables(findOpts) 464 if err != nil { 465 return nil, err 466 } 467 468 if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { 469 return nil, fmt.Errorf("Route Table %q is gone, or route does not exist.", 470 routeTableID) 471 } 472 473 if cidr != "" { 474 for _, route := range (*resp.RouteTables[0]).Routes { 475 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { 476 return route, nil 477 } 478 } 479 480 return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ 481 "and destination CIDR block (%s).", rtbid, cidr) 482 } 483 484 if ipv6cidr != "" { 485 for _, route := range (*resp.RouteTables[0]).Routes { 486 if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == ipv6cidr { 487 return route, nil 488 } 489 } 490 491 return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ 492 "and destination IPv6 CIDR block (%s).", rtbid, ipv6cidr) 493 } 494 495 return nil, fmt.Errorf("When trying to find a matching route for Route Table %q "+ 496 "you need to specify a CIDR block of IPv6 CIDR Block", rtbid) 497 498 }