github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/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("Error: invalid target type specified.")
   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(15*time.Second, 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  	route, err := findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string))
   199  	if err != nil {
   200  		return err
   201  	}
   202  	resourceAwsRouteSetResourceData(d, route)
   203  	return nil
   204  }
   205  
   206  func resourceAwsRouteSetResourceData(d *schema.ResourceData, route *ec2.Route) {
   207  	d.Set("destination_prefix_list_id", route.DestinationPrefixListId)
   208  	d.Set("gateway_id", route.GatewayId)
   209  	d.Set("nat_gateway_id", route.NatGatewayId)
   210  	d.Set("instance_id", route.InstanceId)
   211  	d.Set("instance_owner_id", route.InstanceOwnerId)
   212  	d.Set("network_interface_id", route.NetworkInterfaceId)
   213  	d.Set("origin", route.Origin)
   214  	d.Set("state", route.State)
   215  	d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId)
   216  }
   217  
   218  func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error {
   219  	conn := meta.(*AWSClient).ec2conn
   220  	var numTargets int
   221  	var setTarget string
   222  	allowedTargets := []string{
   223  		"gateway_id",
   224  		"nat_gateway_id",
   225  		"instance_id",
   226  		"network_interface_id",
   227  		"vpc_peering_connection_id",
   228  	}
   229  	replaceOpts := &ec2.ReplaceRouteInput{}
   230  
   231  	// Check if more than 1 target is specified
   232  	for _, target := range allowedTargets {
   233  		if len(d.Get(target).(string)) > 0 {
   234  			numTargets++
   235  			setTarget = target
   236  		}
   237  	}
   238  
   239  	if numTargets > 1 {
   240  		return routeTargetValidationError
   241  	}
   242  
   243  	// Formulate ReplaceRouteInput based on the target type
   244  	switch setTarget {
   245  	case "gateway_id":
   246  		replaceOpts = &ec2.ReplaceRouteInput{
   247  			RouteTableId:         aws.String(d.Get("route_table_id").(string)),
   248  			DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)),
   249  			GatewayId:            aws.String(d.Get("gateway_id").(string)),
   250  		}
   251  	case "nat_gateway_id":
   252  		replaceOpts = &ec2.ReplaceRouteInput{
   253  			RouteTableId:         aws.String(d.Get("route_table_id").(string)),
   254  			DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)),
   255  			NatGatewayId:         aws.String(d.Get("nat_gateway_id").(string)),
   256  		}
   257  	case "instance_id":
   258  		replaceOpts = &ec2.ReplaceRouteInput{
   259  			RouteTableId:         aws.String(d.Get("route_table_id").(string)),
   260  			DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)),
   261  			InstanceId:           aws.String(d.Get("instance_id").(string)),
   262  			//NOOP: Ensure we don't blow away network interface id that is set after instance is launched
   263  			NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)),
   264  		}
   265  	case "network_interface_id":
   266  		replaceOpts = &ec2.ReplaceRouteInput{
   267  			RouteTableId:         aws.String(d.Get("route_table_id").(string)),
   268  			DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)),
   269  			NetworkInterfaceId:   aws.String(d.Get("network_interface_id").(string)),
   270  		}
   271  	case "vpc_peering_connection_id":
   272  		replaceOpts = &ec2.ReplaceRouteInput{
   273  			RouteTableId:           aws.String(d.Get("route_table_id").(string)),
   274  			DestinationCidrBlock:   aws.String(d.Get("destination_cidr_block").(string)),
   275  			VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)),
   276  		}
   277  	default:
   278  		return fmt.Errorf("Error: invalid target type specified.")
   279  	}
   280  	log.Printf("[DEBUG] Route replace config: %s", replaceOpts)
   281  
   282  	// Replace the route
   283  	_, err := conn.ReplaceRoute(replaceOpts)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error {
   292  	conn := meta.(*AWSClient).ec2conn
   293  
   294  	deleteOpts := &ec2.DeleteRouteInput{
   295  		RouteTableId:         aws.String(d.Get("route_table_id").(string)),
   296  		DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)),
   297  	}
   298  	log.Printf("[DEBUG] Route delete opts: %s", deleteOpts)
   299  
   300  	var err error
   301  	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
   302  		log.Printf("[DEBUG] Trying to delete route with opts %s", deleteOpts)
   303  		resp, err := conn.DeleteRoute(deleteOpts)
   304  		log.Printf("[DEBUG] Route delete result: %s", resp)
   305  
   306  		if err == nil {
   307  			return nil
   308  		}
   309  
   310  		ec2err, ok := err.(awserr.Error)
   311  		if !ok {
   312  			return resource.NonRetryableError(err)
   313  		}
   314  		if ec2err.Code() == "InvalidParameterException" {
   315  			log.Printf("[DEBUG] Trying to delete route again: %q",
   316  				ec2err.Message())
   317  			return resource.RetryableError(err)
   318  		}
   319  
   320  		return resource.NonRetryableError(err)
   321  	})
   322  
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	d.SetId("")
   328  	return nil
   329  }
   330  
   331  func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, error) {
   332  	conn := meta.(*AWSClient).ec2conn
   333  	routeTableId := d.Get("route_table_id").(string)
   334  
   335  	findOpts := &ec2.DescribeRouteTablesInput{
   336  		RouteTableIds: []*string{&routeTableId},
   337  	}
   338  
   339  	res, err := conn.DescribeRouteTables(findOpts)
   340  	if err != nil {
   341  		return false, fmt.Errorf("Error while checking if route exists: %s", err)
   342  	}
   343  
   344  	if len(res.RouteTables) < 1 || res.RouteTables[0] == nil {
   345  		log.Printf("[WARN] Route table %s is gone, so route does not exist.",
   346  			routeTableId)
   347  		return false, nil
   348  	}
   349  
   350  	cidr := d.Get("destination_cidr_block").(string)
   351  	for _, route := range (*res.RouteTables[0]).Routes {
   352  		if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr {
   353  			return true, nil
   354  		}
   355  	}
   356  
   357  	return false, nil
   358  }
   359  
   360  // Create an ID for a route
   361  func routeIDHash(d *schema.ResourceData, r *ec2.Route) string {
   362  	return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock))
   363  }
   364  
   365  // Helper: retrieve a route
   366  func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, error) {
   367  	routeTableID := rtbid
   368  
   369  	findOpts := &ec2.DescribeRouteTablesInput{
   370  		RouteTableIds: []*string{&routeTableID},
   371  	}
   372  
   373  	resp, err := conn.DescribeRouteTables(findOpts)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	if len(resp.RouteTables) < 1 || resp.RouteTables[0] == nil {
   379  		return nil, fmt.Errorf("Route table %s is gone, so route does not exist.",
   380  			routeTableID)
   381  	}
   382  
   383  	for _, route := range (*resp.RouteTables[0]).Routes {
   384  		if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr {
   385  			return route, nil
   386  		}
   387  	}
   388  
   389  	return nil, fmt.Errorf(
   390  		`error finding matching route for Route table (%s) and destination CIDR block (%s)`,
   391  		rtbid, cidr)
   392  }