diff --git a/builtin/providers/aws/resource_aws_route.go b/builtin/providers/aws/resource_aws_route.go index 915e12e48..b33dfe3b7 100644 --- a/builtin/providers/aws/resource_aws_route.go +++ b/builtin/providers/aws/resource_aws_route.go @@ -16,7 +16,7 @@ import ( // How long to sleep if a limit-exceeded event happens var routeTargetValidationError = errors.New("Error: more than 1 target specified. Only 1 of gateway_id, " + - "nat_gateway_id, instance_id, network_interface_id, route_table_id or " + + "egress_only_gateway_id, nat_gateway_id, instance_id, network_interface_id, route_table_id or " + "vpc_peering_connection_id is allowed.") // AWS Route resource Schema declaration @@ -29,62 +29,73 @@ func resourceAwsRoute() *schema.Resource { Exists: resourceAwsRouteExists, Schema: map[string]*schema.Schema{ - "destination_cidr_block": &schema.Schema{ + "destination_cidr_block": { Type: schema.TypeString, - Required: true, + Optional: true, + ForceNew: true, + }, + "destination_ipv6_cidr_block": { + Type: schema.TypeString, + Optional: true, ForceNew: true, }, - "destination_prefix_list_id": &schema.Schema{ + "destination_prefix_list_id": { Type: schema.TypeString, Computed: true, }, - "gateway_id": &schema.Schema{ + "gateway_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "nat_gateway_id": &schema.Schema{ + "egress_only_gateway_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "instance_id": &schema.Schema{ + "nat_gateway_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "instance_owner_id": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - - "network_interface_id": &schema.Schema{ + "instance_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "origin": &schema.Schema{ + "instance_owner_id": { Type: schema.TypeString, Computed: true, }, - "state": &schema.Schema{ + "network_interface_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "origin": { Type: schema.TypeString, Computed: true, }, - "route_table_id": &schema.Schema{ + "state": { + Type: schema.TypeString, + Computed: true, + }, + + "route_table_id": { Type: schema.TypeString, Required: true, }, - "vpc_peering_connection_id": &schema.Schema{ + "vpc_peering_connection_id": { Type: schema.TypeString, Optional: true, }, @@ -97,6 +108,7 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { var numTargets int var setTarget string allowedTargets := []string{ + "egress_only_gateway_id", "gateway_id", "nat_gateway_id", "instance_id", @@ -125,6 +137,12 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), GatewayId: aws.String(d.Get("gateway_id").(string)), } + case "egress_only_gateway_id": + createOpts = &ec2.CreateRouteInput{ + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), + EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), + } case "nat_gateway_id": createOpts = &ec2.CreateRouteInput{ RouteTableId: aws.String(d.Get("route_table_id").(string)), @@ -180,12 +198,25 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { } var route *ec2.Route - err = resource.Retry(2*time.Minute, func() *resource.RetryError { - route, err = findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) - return resource.RetryableError(err) - }) - if err != nil { - return fmt.Errorf("Error finding route after creating it: %s", err) + + if v, ok := d.GetOk("destination_cidr_block"); ok { + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + route, err = findResourceRoute(conn, d.Get("route_table_id").(string), v.(string), "") + return resource.RetryableError(err) + }) + if err != nil { + return fmt.Errorf("Error finding route after creating it: %s", err) + } + } + + if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { + err = resource.Retry(2*time.Minute, func() *resource.RetryError { + route, err = findResourceRoute(conn, d.Get("route_table_id").(string), "", v.(string)) + return resource.RetryableError(err) + }) + if err != nil { + return fmt.Errorf("Error finding route after creating it: %s", err) + } } d.SetId(routeIDHash(d, route)) @@ -197,7 +228,10 @@ func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn routeTableId := d.Get("route_table_id").(string) - route, err := findResourceRoute(conn, routeTableId, d.Get("destination_cidr_block").(string)) + destinationCidrBlock := d.Get("destination_cidr_block").(string) + destinationIpv6CidrBlock := d.Get("destination_ipv6_cidr_block").(string) + + route, err := findResourceRoute(conn, routeTableId, destinationCidrBlock, destinationIpv6CidrBlock) if err != nil { if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidRouteTableID.NotFound" { log.Printf("[WARN] Route Table %q could not be found. Removing Route from state.", @@ -214,6 +248,7 @@ func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { func resourceAwsRouteSetResourceData(d *schema.ResourceData, route *ec2.Route) { d.Set("destination_prefix_list_id", route.DestinationPrefixListId) d.Set("gateway_id", route.GatewayId) + d.Set("egress_only_gateway_id", route.EgressOnlyInternetGatewayId) d.Set("nat_gateway_id", route.NatGatewayId) d.Set("instance_id", route.InstanceId) d.Set("instance_owner_id", route.InstanceOwnerId) @@ -229,6 +264,7 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { var setTarget string allowedTargets := []string{ + "egress_only_gateway_id", "gateway_id", "nat_gateway_id", "network_interface_id", @@ -267,6 +303,12 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), GatewayId: aws.String(d.Get("gateway_id").(string)), } + case "egress_only_gateway_id": + replaceOpts = &ec2.ReplaceRouteInput{ + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationIpv6CidrBlock: aws.String(d.Get("destination_ipv6_cidr_block").(string)), + EgressOnlyInternetGatewayId: aws.String(d.Get("egress_only_gateway_id").(string)), + } case "nat_gateway_id": replaceOpts = &ec2.ReplaceRouteInput{ RouteTableId: aws.String(d.Get("route_table_id").(string)), @@ -309,8 +351,13 @@ func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn deleteOpts := &ec2.DeleteRouteInput{ - RouteTableId: aws.String(d.Get("route_table_id").(string)), - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + } + if v, ok := d.GetOk("destination_cidr_block"); ok { + deleteOpts.DestinationCidrBlock = aws.String(v.(string)) + } + if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { + deleteOpts.DestinationIpv6CidrBlock = aws.String(v.(string)) } log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) @@ -368,10 +415,19 @@ func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, err return false, nil } - cidr := d.Get("destination_cidr_block").(string) - for _, route := range (*res.RouteTables[0]).Routes { - if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { - return true, nil + if v, ok := d.GetOk("destination_cidr_block"); ok { + for _, route := range (*res.RouteTables[0]).Routes { + if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == v.(string) { + return true, nil + } + } + } + + if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { + for _, route := range (*res.RouteTables[0]).Routes { + if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == v.(string) { + return true, nil + } } } @@ -380,11 +436,16 @@ func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, err // Create an ID for a route func routeIDHash(d *schema.ResourceData, r *ec2.Route) string { + + if r.DestinationIpv6CidrBlock != nil && *r.DestinationIpv6CidrBlock != "" { + return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationIpv6CidrBlock)) + } + return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) } // Helper: retrieve a route -func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, error) { +func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string, ipv6cidr string) (*ec2.Route, error) { routeTableID := rtbid findOpts := &ec2.DescribeRouteTablesInput{ @@ -401,12 +462,29 @@ func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, er routeTableID) } - for _, route := range (*resp.RouteTables[0]).Routes { - if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { - return route, nil + if cidr != "" { + for _, route := range (*resp.RouteTables[0]).Routes { + if route.DestinationCidrBlock != nil && *route.DestinationCidrBlock == cidr { + return route, nil + } } + + return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ + "and destination CIDR block (%s).", rtbid, cidr) } - return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ - "and destination CIDR block (%s).", rtbid, cidr) + if ipv6cidr != "" { + for _, route := range (*resp.RouteTables[0]).Routes { + if route.DestinationIpv6CidrBlock != nil && *route.DestinationIpv6CidrBlock == ipv6cidr { + return route, nil + } + } + + return nil, fmt.Errorf("Unable to find matching route for Route Table (%s) "+ + "and destination IPv6 CIDR block (%s).", rtbid, ipv6cidr) + } + + return nil, fmt.Errorf("When trying to find a matching route for Route Table %q "+ + "you need to specify a CIDR block of IPv6 CIDR Block", rtbid) + } diff --git a/builtin/providers/aws/resource_aws_route_test.go b/builtin/providers/aws/resource_aws_route_test.go index d7a2c0b99..a8bc00373 100644 --- a/builtin/providers/aws/resource_aws_route_test.go +++ b/builtin/providers/aws/resource_aws_route_test.go @@ -38,7 +38,7 @@ func TestAccAWSRoute_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSRouteBasicConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.bar", &route), @@ -49,6 +49,43 @@ func TestAccAWSRoute_basic(t *testing.T) { }) } +func TestAccAWSRoute_ipv6Support(t *testing.T) { + var route ec2.Route + + //aws creates a default route + testCheck := func(s *terraform.State) error { + + name := "aws_egress_only_internet_gateway.foo" + gwres, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s\n", name) + } + + if *route.EgressOnlyInternetGatewayId != gwres.Primary.ID { + return fmt.Errorf("Egress Only Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.EgressOnlyInternetGatewayId) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSRouteConfigIpv6, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists("aws_route.bar", &route), + testCheck, + ), + }, + }, + }) +} + func TestAccAWSRoute_changeCidr(t *testing.T) { var route ec2.Route var routeTable ec2.RouteTable @@ -101,14 +138,14 @@ func TestAccAWSRoute_changeCidr(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSRouteBasicConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.bar", &route), testCheck, ), }, - resource.TestStep{ + { Config: testAccAWSRouteBasicConfigChangeCidr, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.bar", &route), @@ -139,14 +176,14 @@ func TestAccAWSRoute_noopdiff(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSRouteNoopChange, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.test", &route), testCheck, ), }, - resource.TestStep{ + { Config: testAccAWSRouteNoopChange, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.test", &route), @@ -166,7 +203,7 @@ func TestAccAWSRoute_doesNotCrashWithVPCEndpoint(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSRouteWithVPCEndpoint, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.bar", &route), @@ -192,6 +229,7 @@ func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc conn, rs.Primary.Attributes["route_table_id"], rs.Primary.Attributes["destination_cidr_block"], + rs.Primary.Attributes["destination_ipv6_cidr_block"], ) if err != nil { @@ -219,6 +257,7 @@ func testAccCheckAWSRouteDestroy(s *terraform.State) error { conn, rs.Primary.Attributes["route_table_id"], rs.Primary.Attributes["destination_cidr_block"], + rs.Primary.Attributes["destination_ipv6_cidr_block"], ) if route == nil && err == nil { @@ -249,6 +288,29 @@ resource "aws_route" "bar" { } `) +var testAccAWSRouteConfigIpv6 = fmt.Sprintf(` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_egress_only_internet_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_route_table" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_route" "bar" { + route_table_id = "${aws_route_table.foo.id}" + destination_ipv6_cidr_block = "::/0" + egress_only_gateway_id = "${aws_egress_only_internet_gateway.foo.id}" +} + + +`) + var testAccAWSRouteBasicConfigChangeCidr = fmt.Sprint(` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" diff --git a/website/source/docs/providers/aws/r/route.html.markdown b/website/source/docs/providers/aws/r/route.html.markdown index c33c1c6be..b6589298f 100644 --- a/website/source/docs/providers/aws/r/route.html.markdown +++ b/website/source/docs/providers/aws/r/route.html.markdown @@ -27,19 +27,40 @@ resource "aws_route" "r" { } ``` +##Example IPv6 Usage: + +``` +resource "aws_vpc" "vpc" { + cidr_block = "10.1.0.0/16" + assign_generated_ipv6_cidr_block = true +} + +resource "aws_egress_only_internet_gateway" "egress" { + vpc_id = "${aws_vpc.vpc.id}" +} + +resource "aws_route" "r" { + route_table_id = "rtb-4fbb3ac4" + destination_ipv6_cidr_block = "::/0" + egress_only_gateway_id = "${aws_egress_only_internet_gateway.egress.id}" +} +``` + ## Argument Reference The following arguments are supported: * `route_table_id` - (Required) The ID of the routing table. -* `destination_cidr_block` - (Required) The destination CIDR block. +* `destination_cidr_block` - (Optional) The destination CIDR block. +* `destination_ipv6_cidr_block` - (Optional) The destination IPv6 CIDR block. * `vpc_peering_connection_id` - (Optional) An ID of a VPC peering connection. +* `egress_only_gateway_id` - (Optional) An ID of a VPC Egress Only Internet Gateway. * `gateway_id` - (Optional) An ID of a VPC internet gateway or a virtual private gateway. * `nat_gateway_id` - (Optional) An ID of a VPC NAT gateway. * `instance_id` - (Optional) An ID of an EC2 instance. * `network_interface_id` - (Optional) An ID of a network interface. -Each route must contain either a `gateway_id`, a `nat_gateway_id`, an +Each route must contain either a `gateway_id`, `egress_only_gateway_id` a `nat_gateway_id`, an `instance_id` or a `vpc_peering_connection_id` or a `network_interface_id`. Note that the default route, mapping the VPC's CIDR block to "local", is created implicitly and cannot be specified. @@ -53,7 +74,9 @@ will be exported as an attribute once the resource is created. * `route_table_id` - The ID of the routing table. * `destination_cidr_block` - The destination CIDR block. +* `destination_ipv6_cidr_block` - The destination IPv6 CIDR block. * `vpc_peering_connection_id` - An ID of a VPC peering connection. +* `egress_only_gateway_id` - An ID of a VPC Egress Only Internet Gateway. * `gateway_id` - An ID of a VPC internet gateway or a virtual private gateway. * `nat_gateway_id` - An ID of a VPC NAT gateway. * `instance_id` - An ID of a NAT instance.