github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 "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": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: true, 36 }, 37 38 "destination_prefix_list_id": &schema.Schema{ 39 Type: schema.TypeString, 40 Computed: true, 41 }, 42 43 "gateway_id": &schema.Schema{ 44 Type: schema.TypeString, 45 Optional: true, 46 Computed: true, 47 }, 48 49 "nat_gateway_id": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 Computed: true, 53 }, 54 55 "instance_id": &schema.Schema{ 56 Type: schema.TypeString, 57 Optional: true, 58 Computed: true, 59 }, 60 61 "instance_owner_id": &schema.Schema{ 62 Type: schema.TypeString, 63 Computed: true, 64 }, 65 66 "network_interface_id": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 Computed: true, 70 }, 71 72 "origin": &schema.Schema{ 73 Type: schema.TypeString, 74 Computed: true, 75 }, 76 77 "state": &schema.Schema{ 78 Type: schema.TypeString, 79 Computed: true, 80 }, 81 82 "route_table_id": &schema.Schema{ 83 Type: schema.TypeString, 84 Required: true, 85 }, 86 87 "vpc_peering_connection_id": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 }, 91 }, 92 } 93 } 94 95 func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { 96 conn := meta.(*AWSClient).ec2conn 97 var numTargets int 98 var setTarget string 99 allowedTargets := []string{ 100 "gateway_id", 101 "nat_gateway_id", 102 "instance_id", 103 "network_interface_id", 104 "vpc_peering_connection_id", 105 } 106 107 // Check if more than 1 target is specified 108 for _, target := range allowedTargets { 109 if len(d.Get(target).(string)) > 0 { 110 numTargets++ 111 setTarget = target 112 } 113 } 114 115 if numTargets > 1 { 116 return routeTargetValidationError 117 } 118 119 createOpts := &ec2.CreateRouteInput{} 120 // Formulate CreateRouteInput based on the target type 121 switch setTarget { 122 case "gateway_id": 123 createOpts = &ec2.CreateRouteInput{ 124 RouteTableId: aws.String(d.Get("route_table_id").(string)), 125 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 126 GatewayId: aws.String(d.Get("gateway_id").(string)), 127 } 128 case "nat_gateway_id": 129 createOpts = &ec2.CreateRouteInput{ 130 RouteTableId: aws.String(d.Get("route_table_id").(string)), 131 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 132 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 133 } 134 case "instance_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 InstanceId: aws.String(d.Get("instance_id").(string)), 139 } 140 case "network_interface_id": 141 createOpts = &ec2.CreateRouteInput{ 142 RouteTableId: aws.String(d.Get("route_table_id").(string)), 143 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 144 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 145 } 146 case "vpc_peering_connection_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 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 151 } 152 default: 153 return fmt.Errorf("An invalid target type specified: %s", setTarget) 154 } 155 log.Printf("[DEBUG] Route create config: %s", createOpts) 156 157 // Create the route 158 var err error 159 160 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 161 _, err = conn.CreateRoute(createOpts) 162 163 if err != nil { 164 ec2err, ok := err.(awserr.Error) 165 if !ok { 166 return resource.NonRetryableError(err) 167 } 168 if ec2err.Code() == "InvalidParameterException" { 169 log.Printf("[DEBUG] Trying to create route again: %q", ec2err.Message()) 170 return resource.RetryableError(err) 171 } 172 173 return resource.NonRetryableError(err) 174 } 175 176 return nil 177 }) 178 if err != nil { 179 return fmt.Errorf("Error creating route: %s", err) 180 } 181 182 var route *ec2.Route 183 err = resource.Retry(2*time.Minute, func() *resource.RetryError { 184 route, err = findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) 185 return resource.RetryableError(err) 186 }) 187 if err != nil { 188 return fmt.Errorf("Error finding route after creating it: %s", err) 189 } 190 191 d.SetId(routeIDHash(d, route)) 192 resourceAwsRouteSetResourceData(d, route) 193 return nil 194 } 195 196 func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { 197 conn := meta.(*AWSClient).ec2conn 198 routeTableId := d.Get("route_table_id").(string) 199 200 route, err := findResourceRoute(conn, routeTableId, d.Get("destination_cidr_block").(string)) 201 if err != nil { 202 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 203 log.Printf("[WARN] Route Table %q could not be found. Removing Route from state.", 204 routeTableId) 205 d.SetId("") 206 return nil 207 } 208 return err 209 } 210 resourceAwsRouteSetResourceData(d, route) 211 return nil 212 } 213 214 func resourceAwsRouteSetResourceData(d *schema.ResourceData, route *ec2.Route) { 215 d.Set("destination_prefix_list_id", route.DestinationPrefixListId) 216 d.Set("gateway_id", route.GatewayId) 217 d.Set("nat_gateway_id", route.NatGatewayId) 218 d.Set("instance_id", route.InstanceId) 219 d.Set("instance_owner_id", route.InstanceOwnerId) 220 d.Set("network_interface_id", route.NetworkInterfaceId) 221 d.Set("origin", route.Origin) 222 d.Set("state", route.State) 223 d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId) 224 } 225 226 func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { 227 conn := meta.(*AWSClient).ec2conn 228 var numTargets int 229 var setTarget string 230 231 allowedTargets := []string{ 232 "gateway_id", 233 "nat_gateway_id", 234 "network_interface_id", 235 "instance_id", 236 "vpc_peering_connection_id", 237 } 238 replaceOpts := &ec2.ReplaceRouteInput{} 239 240 // Check if more than 1 target is specified 241 for _, target := range allowedTargets { 242 if len(d.Get(target).(string)) > 0 { 243 numTargets++ 244 setTarget = target 245 } 246 } 247 248 switch setTarget { 249 //instance_id is a special case due to the fact that AWS will "discover" the network_interace_id 250 //when it creates the route and return that data. In the case of an update, we should ignore the 251 //existing network_interface_id 252 case "instance_id": 253 if numTargets > 2 || (numTargets == 2 && len(d.Get("network_interface_id").(string)) == 0) { 254 return routeTargetValidationError 255 } 256 default: 257 if numTargets > 1 { 258 return routeTargetValidationError 259 } 260 } 261 262 // Formulate ReplaceRouteInput based on the target type 263 switch setTarget { 264 case "gateway_id": 265 replaceOpts = &ec2.ReplaceRouteInput{ 266 RouteTableId: aws.String(d.Get("route_table_id").(string)), 267 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 268 GatewayId: aws.String(d.Get("gateway_id").(string)), 269 } 270 case "nat_gateway_id": 271 replaceOpts = &ec2.ReplaceRouteInput{ 272 RouteTableId: aws.String(d.Get("route_table_id").(string)), 273 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 274 NatGatewayId: aws.String(d.Get("nat_gateway_id").(string)), 275 } 276 case "instance_id": 277 replaceOpts = &ec2.ReplaceRouteInput{ 278 RouteTableId: aws.String(d.Get("route_table_id").(string)), 279 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 280 InstanceId: aws.String(d.Get("instance_id").(string)), 281 } 282 case "network_interface_id": 283 replaceOpts = &ec2.ReplaceRouteInput{ 284 RouteTableId: aws.String(d.Get("route_table_id").(string)), 285 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 286 NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), 287 } 288 case "vpc_peering_connection_id": 289 replaceOpts = &ec2.ReplaceRouteInput{ 290 RouteTableId: aws.String(d.Get("route_table_id").(string)), 291 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 292 VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), 293 } 294 default: 295 return fmt.Errorf("An invalid target type specified: %s", setTarget) 296 } 297 log.Printf("[DEBUG] Route replace config: %s", replaceOpts) 298 299 // Replace the route 300 _, err := conn.ReplaceRoute(replaceOpts) 301 if err != nil { 302 return err 303 } 304 305 return nil 306 } 307 308 func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { 309 conn := meta.(*AWSClient).ec2conn 310 311 deleteOpts := &ec2.DeleteRouteInput{ 312 RouteTableId: aws.String(d.Get("route_table_id").(string)), 313 DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), 314 } 315 log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) 316 317 var err error 318 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 319 log.Printf("[DEBUG] Trying to delete route with opts %s", deleteOpts) 320 resp, err := conn.DeleteRoute(deleteOpts) 321 log.Printf("[DEBUG] Route delete result: %s", resp) 322 323 if err == nil { 324 return nil 325 } 326 327 ec2err, ok := err.(awserr.Error) 328 if !ok { 329 return resource.NonRetryableError(err) 330 } 331 if ec2err.Code() == "InvalidParameterException" { 332 log.Printf("[DEBUG] Trying to delete route again: %q", 333 ec2err.Message()) 334 return resource.RetryableError(err) 335 } 336 337 return resource.NonRetryableError(err) 338 }) 339 340 if err != nil { 341 return err 342 } 343 344 d.SetId("") 345 return nil 346 } 347 348 func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, error) { 349 conn := meta.(*AWSClient).ec2conn 350 routeTableId := d.Get("route_table_id").(string) 351 352 findOpts := &ec2.DescribeRouteTablesInput{ 353 RouteTableIds: []*string{&routeTableId}, 354 } 355 356 res, err := conn.DescribeRouteTables(findOpts) 357 if err != nil { 358 if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { 359 log.Printf("[WARN] Route Table %q could not be found.", routeTableId) 360 return false, nil 361 } 362 return false, fmt.Errorf("Error while checking if route exists: %s", err) 363 } 364 365 if len(res.RouteTables) < 1 || res.RouteTables[0] == nil { 366 log.Printf("[WARN] Route Table %q is gone, or route does not exist.", 367 routeTableId) 368 return false, nil 369 } 370 371 cidr := d.Get("destination_cidr_block").(string) 372 for _, route := range (*res.RouteTables[0]).Routes { 373 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { 374 return true, nil 375 } 376 } 377 378 return false, nil 379 } 380 381 // Create an ID for a route 382 func routeIDHash(d *schema.ResourceData, r *ec2.Route) string { 383 return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) 384 } 385 386 // Helper: retrieve a route 387 func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, error) { 388 routeTableID := rtbid 389 390 findOpts := &ec2.DescribeRouteTablesInput{ 391 RouteTableIds: []*string{&routeTableID}, 392 } 393 394 resp, err := conn.DescribeRouteTables(findOpts) 395 if err != nil { 396 return nil, err 397 } 398 399 if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil { 400 return nil, fmt.Errorf("Route Table %q is gone, or route does not exist.", 401 routeTableID) 402 } 403 404 for _, route := range (*resp.RouteTables[0]).Routes { 405 if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { 406 return route, nil 407 } 408 } 409 410 return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ 411 "and destination CIDR block (%s).", rtbid, cidr) 412 }