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  }