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