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  }