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  }