github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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, route_table_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 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 138 GatewayId: aws.String(d.Get("gateway_id").(string)), 139 } 140 case "egress_only_gateway_id": 141 createOpts = &ec2.CreateRouteInput{ 142 RouteTableId: aws.String(d.Get("route_table_id").(string)), 143 DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), 144 EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), 145 } 146 case "nat_gateway_id": 147 createOpts = &ec2.CreateRouteInput{ 148 RouteTableId: aws.String(d.Get("route_table_id").(string)), 149 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 150 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 151 } 152 case "instance_id": 153 createOpts = &ec2.CreateRouteInput{ 154 RouteTableId: aws.String(d.Get("route_table_id").(string)), 155 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 156 InstanceId: aws.String(d.Get("instance_id").(string)), 157 } 158 case "network_interface_id": 159 createOpts = &ec2.CreateRouteInput{ 160 RouteTableId: aws.String(d.Get("route_table_id").(string)), 161 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 162 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 163 } 164 case "vpc_peering_connection_id": 165 createOpts = &ec2.CreateRouteInput{ 166 RouteTableId: aws.String(d.Get("route_table_id").(string)), 167 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 168 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 169 } 170 default: 171 return fmt.Errorf("An invalid target type specified: %s", setTarget) 172 } 173 log.Printf("[DEBUG] Route create config: %s", createOpts) 174 175 // Create the route 176 var err error 177 178 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 179 _, err = conn.CreateRoute(createOpts) 180 181 if err != nil { 182 ec2err, ok := err.(awserr.Error) 183 if !ok { 184 return resource.NonRetryableError(err) 185 } 186 if ec2err.Code() == "InvalidParameterException" { 187 log.Printf("[DEBUG] Trying to create route again: %q", ec2err.Message()) 188 return resource.RetryableError(err) 189 } 190 191 return resource.NonRetryableError(err) 192 } 193 194 return nil 195 }) 196 if err != nil { 197 return fmt.Errorf("Error creating route: %s", err) 198 } 199 200 var route *ec2.Route 201 202 if v, ok := d.GetOk("destination_cidr_block"); ok { 203 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 204 route, err = findResourceRoute(conn, d.Get("route_table_id").(string), v.(string), "") 205 return resource.RetryableError(err) 206 }) 207 if err != nil { 208 return fmt.Errorf("Error finding route after creating it: %s", err) 209 } 210 } 211 212 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 213 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 214 route, err = findResourceRoute(conn, d.Get("route_table_id").(string), "", v.(string)) 215 return resource.RetryableError(err) 216 }) 217 if err != nil { 218 return fmt.Errorf("Error finding route after creating it: %s", err) 219 } 220 } 221 222 d.SetId(routeIDHash(d, route)) 223 resourceAwsRouteSetResourceData(d, route) 224 return nil 225 } 226 227 func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { 228 conn := meta.(*AWSClient).ec2conn 229 routeTableId := d.Get("route_table_id").(string) 230 231 destinationCidrBlock := d.Get("destination_cidr_block").(string) 232 destinationIpv6CidrBlock := d.Get("destination_ipv6_cidr_block").(string) 233 234 route, err := findResourceRoute(conn, routeTableId, destinationCidrBlock, destinationIpv6CidrBlock) 235 if err != nil { 236 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 237 log.Printf("[WARN] Route Table %q could not be found. Removing Route from state.", 238 routeTableId) 239 d.SetId("") 240 return nil 241 } 242 return err 243 } 244 resourceAwsRouteSetResourceData(d, route) 245 return nil 246 } 247 248 func resourceAwsRouteSetResourceData(d *schema.ResourceData, route *ec2.Route) { 249 d.Set("destination_prefix_list_id", route.DestinationPrefixListId) 250 d.Set("gateway_id", route.GatewayId) 251 d.Set("egress_only_gateway_id", route.EgressOnlyInternetGatewayId) 252 d.Set("nat_gateway_id", route.NatGatewayId) 253 d.Set("instance_id", route.InstanceId) 254 d.Set("instance_owner_id", route.InstanceOwnerId) 255 d.Set("network_interface_id", route.NetworkInterfaceId) 256 d.Set("origin", route.Origin) 257 d.Set("state", route.State) 258 d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId) 259 } 260 261 func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { 262 conn := meta.(*AWSClient).ec2conn 263 var numTargets int 264 var setTarget string 265 266 allowedTargets := []string{ 267 "egress_only_gateway_id", 268 "gateway_id", 269 "nat_gateway_id", 270 "network_interface_id", 271 "instance_id", 272 "vpc_peering_connection_id", 273 } 274 replaceOpts := &ec2.ReplaceRouteInput{} 275 276 // Check if more than 1 target is specified 277 for _, target := range allowedTargets { 278 if len(d.Get(target).(string)) > 0 { 279 numTargets++ 280 setTarget = target 281 } 282 } 283 284 switch setTarget { 285 //instance_id is a special case due to the fact that AWS will "discover" the network_interace_id 286 //when it creates the route and return that data. In the case of an update, we should ignore the 287 //existing network_interface_id 288 case "instance_id": 289 if numTargets > 2 || (numTargets == 2 && len(d.Get("network_interface_id").(string)) == 0) { 290 return routeTargetValidationError 291 } 292 default: 293 if numTargets > 1 { 294 return routeTargetValidationError 295 } 296 } 297 298 // Formulate ReplaceRouteInput based on the target type 299 switch setTarget { 300 case "gateway_id": 301 replaceOpts = &ec2.ReplaceRouteInput{ 302 RouteTableId: aws.String(d.Get("route_table_id").(string)), 303 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 304 GatewayId: aws.String(d.Get("gateway_id").(string)), 305 } 306 case "egress_only_gateway_id": 307 replaceOpts = &ec2.ReplaceRouteInput{ 308 RouteTableId: aws.String(d.Get("route_table_id").(string)), 309 DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), 310 EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), 311 } 312 case "nat_gateway_id": 313 replaceOpts = &ec2.ReplaceRouteInput{ 314 RouteTableId: aws.String(d.Get("route_table_id").(string)), 315 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 316 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 317 } 318 case "instance_id": 319 replaceOpts = &ec2.ReplaceRouteInput{ 320 RouteTableId: aws.String(d.Get("route_table_id").(string)), 321 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 322 InstanceId: aws.String(d.Get("instance_id").(string)), 323 } 324 case "network_interface_id": 325 replaceOpts = &ec2.ReplaceRouteInput{ 326 RouteTableId: aws.String(d.Get("route_table_id").(string)), 327 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 328 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 329 } 330 case "vpc_peering_connection_id": 331 replaceOpts = &ec2.ReplaceRouteInput{ 332 RouteTableId: aws.String(d.Get("route_table_id").(string)), 333 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 334 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 335 } 336 default: 337 return fmt.Errorf("An invalid target type specified: %s", setTarget) 338 } 339 log.Printf("[DEBUG] Route replace config: %s", replaceOpts) 340 341 // Replace the route 342 _, err := conn.ReplaceRoute(replaceOpts) 343 if err != nil { 344 return err 345 } 346 347 return nil 348 } 349 350 func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { 351 conn := meta.(*AWSClient).ec2conn 352 353 deleteOpts := &ec2.DeleteRouteInput{ 354 RouteTableId: aws.String(d.Get("route_table_id").(string)), 355 } 356 if v, ok := d.GetOk("destination_cidr_block"); ok { 357 deleteOpts.DestinationCidrBlock = aws.String(v.(string)) 358 } 359 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 360 deleteOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) 361 } 362 log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) 363 364 var err error 365 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 366 log.Printf("[DEBUG] Trying to delete route with opts %s", deleteOpts) 367 resp, err := conn.DeleteRoute(deleteOpts) 368 log.Printf("[DEBUG] Route delete result: %s", resp) 369 370 if err == nil { 371 return nil 372 } 373 374 ec2err, ok := err.(awserr.Error) 375 if !ok { 376 return resource.NonRetryableError(err) 377 } 378 if ec2err.Code() == "InvalidParameterException" { 379 log.Printf("[DEBUG] Trying to delete route again: %q", 380 ec2err.Message()) 381 return resource.RetryableError(err) 382 } 383 384 return resource.NonRetryableError(err) 385 }) 386 387 if err != nil { 388 return err 389 } 390 391 d.SetId("") 392 return nil 393 } 394 395 func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, error) { 396 conn := meta.(*AWSClient).ec2conn 397 routeTableId := d.Get("route_table_id").(string) 398 399 findOpts := &ec2.DescribeRouteTablesInput{ 400 RouteTableIds: []*string{&routeTableId}, 401 } 402 403 res, err := conn.DescribeRouteTables(findOpts) 404 if err != nil { 405 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 406 log.Printf("[WARN] Route Table %q could not be found.", routeTableId) 407 return false, nil 408 } 409 return false, fmt.Errorf("Error while checking if route exists: %s", err) 410 } 411 412 if len(res.RouteTables) < 1 || res.RouteTables[0] == nil { 413 log.Printf("[WARN] Route Table %q is gone, or route does not exist.", 414 routeTableId) 415 return false, nil 416 } 417 418 if v, ok := d.GetOk("destination_cidr_block"); ok { 419 for _, route := range (*res.RouteTables[0]).Routes { 420 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == v.(string) { 421 return true, nil 422 } 423 } 424 } 425 426 if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { 427 for _, route := range (*res.RouteTables[0]).Routes { 428 if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == v.(string) { 429 return true, nil 430 } 431 } 432 } 433 434 return false, nil 435 } 436 437 // Create an ID for a route 438 func routeIDHash(d *schema.ResourceData, r *ec2.Route) string { 439 440 if r.DestinationIpv6CidrBlock != nil && *r.DestinationIpv6CidrBlock != "" { 441 return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationIpv6CidrBlock)) 442 } 443 444 return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) 445 } 446 447 // Helper: retrieve a route 448 func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string, ipv6cidr string) (*ec2.Route, error) { 449 routeTableID := rtbid 450 451 findOpts := &ec2.DescribeRouteTablesInput{ 452 RouteTableIds: []*string{&routeTableID}, 453 } 454 455 resp, err := conn.DescribeRouteTables(findOpts) 456 if err != nil { 457 return nil, err 458 } 459 460 if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { 461 return nil, fmt.Errorf("Route Table %q is gone, or route does not exist.", 462 routeTableID) 463 } 464 465 if cidr != "" { 466 for _, route := range (*resp.RouteTables[0]).Routes { 467 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { 468 return route, nil 469 } 470 } 471 472 return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ 473 "and destination CIDR block (%s).", rtbid, cidr) 474 } 475 476 if ipv6cidr != "" { 477 for _, route := range (*resp.RouteTables[0]).Routes { 478 if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == ipv6cidr { 479 return route, nil 480 } 481 } 482 483 return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ 484 "and destination IPv6 CIDR block (%s).", rtbid, ipv6cidr) 485 } 486 487 return nil, fmt.Errorf("When trying to find a matching route for Route Table %q "+ 488 "you need to specify a CIDR block of IPv6 CIDR Block", rtbid) 489 490 }