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