diff --git a/builtin/providers/aws/resource_aws_route.go b/builtin/providers/aws/resource_aws_route.go index a85388c0e..62d81a215 100644 --- a/builtin/providers/aws/resource_aws_route.go +++ b/builtin/providers/aws/resource_aws_route.go @@ -11,13 +11,14 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -// AWS Route resource Schema delcaration +// AWS Route resource Schema declaration func resourceAwsRoute() *schema.Resource { return &schema.Resource{ Create: resourceAwsRouteCreate, Read: resourceAwsRouteRead, Update: resourceAwsRouteUpdate, Delete: resourceAwsRouteDelete, + Exists: resourceAwsRouteExists, Schema: map[string]*schema.Schema{ "destination_cidr_block": &schema.Schema{ @@ -83,7 +84,6 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { "network_interface_id", "vpc_peering_connection_id", } - createOpts := &ec2.CreateRouteInput{} // Check if more than 1 target is specified for _, target := range allowedTargets { @@ -99,31 +99,32 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { "vpc_peering_connection_id is allowed.") } + createOpts := &ec2.CreateRouteInput{} // Formulate CreateRouteInput based on the target type switch setTarget { case "gateway_id": createOpts = &ec2.CreateRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - GatewayID: aws.String(d.Get("gateway_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + GatewayId: aws.String(d.Get("gateway_id").(string)), } case "instance_id": createOpts = &ec2.CreateRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - InstanceID: aws.String(d.Get("instance_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + InstanceId: aws.String(d.Get("instance_id").(string)), } case "network_interface_id": createOpts = &ec2.CreateRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - NetworkInterfaceID: aws.String(d.Get("network_interface_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), } case "vpc_peering_connection_id": createOpts = &ec2.CreateRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - VPCPeeringConnectionID: aws.String(d.Get("vpc_peering_connection_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), } default: fmt.Errorf("Error: invalid target type specified.") @@ -153,19 +154,23 @@ func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { return err } - d.Set("destination_prefix_list_id", route.DestinationPrefixListID) - d.Set("gateway_id", route.DestinationPrefixListID) - d.Set("instance_id", route.InstanceID) - d.Set("instance_owner_id", route.InstanceOwnerID) - d.Set("network_interface_id", route.NetworkInterfaceID) + d.Set("destination_prefix_list_id", route.DestinationPrefixListId) + d.Set("gateway_id", route.GatewayId) + d.Set("instance_id", route.InstanceId) + d.Set("instance_owner_id", route.InstanceOwnerId) + d.Set("network_interface_id", route.NetworkInterfaceId) d.Set("origin", route.Origin) d.Set("state", route.State) - d.Set("vpc_peering_connection_id", route.VPCPeeringConnectionID) + d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId) return nil } func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { + if d.HasChange("destination_cidr_block") { + return resourceAwsRouteRecreate(d, meta) + } + conn := meta.(*AWSClient).ec2conn var numTargets int var setTarget string @@ -195,27 +200,29 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { switch setTarget { case "gateway_id": replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - GatewayID: aws.String(d.Get("gateway_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + GatewayId: aws.String(d.Get("gateway_id").(string)), } case "instance_id": replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - InstanceID: aws.String(d.Get("instance_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + InstanceId: aws.String(d.Get("instance_id").(string)), + //NOOP: Ensure we don't blow away network interface id that is set after instance is launched + NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), } case "network_interface_id": replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - NetworkInterfaceID: aws.String(d.Get("network_interface_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), } case "vpc_peering_connection_id": replaceOpts = &ec2.ReplaceRouteInput{ - RouteTableID: aws.String(d.Get("route_table_id").(string)), - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - VPCPeeringConnectionID: aws.String(d.Get("vpc_peering_connection_id").(string)), + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + VpcPeeringConnectionId: aws.String(d.Get("vpc_peering_connection_id").(string)), } default: fmt.Errorf("Error: invalid target type specified.") @@ -231,28 +238,65 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { +func resourceAwsRouteRecreate(d *schema.ResourceData, meta interface{}) error { + //Destination Cidr is used for identification + // if changed, we should delete the old route, recreate the new route conn := meta.(*AWSClient).ec2conn - deleteOpts := &ec2.DeleteRouteInput{ - DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), - RouteTableID: aws.String(d.Get("route_table_id").(string)), - } - log.Printf("[DEBUG] Route delete opts: %s", awsutil.Prettify(deleteOpts)) - resp, err := conn.DeleteRoute(deleteOpts) - log.Printf("[DEBUG] Route delete result: %s", awsutil.Prettify(resp)) + oc, _ := d.GetChange("destination_cidr_block") + + var oldRtId interface{} + if d.HasChange("route_table_id") { + oldRtId, _ = d.GetChange("route_table_id") + } else { + oldRtId = d.Get("route_table_id") + } + + if err := deleteAwsRoute(conn, oldRtId.(string), oc.(string)); err != nil { + return err + } + d.SetId("") + + return resourceAwsRouteCreate(d, meta) +} + +func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { + err := deleteAwsRoute(meta.(*AWSClient).ec2conn, + d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) if err != nil { return err } d.SetId("") - return nil } +func resourceAwsRouteExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*AWSClient).ec2conn + routeTableId := d.Get("route_table_id").(string) + + findOpts := &ec2.DescribeRouteTablesInput{ + RouteTableIds: []*string{&routeTableId}, + } + + res, err := conn.DescribeRouteTables(findOpts) + if err != nil { + return false, err + } + + cidr := d.Get("destination_cidr_block").(string) + for _, route := range (*res.RouteTables[0]).Routes { + if *route.DestinationCidrBlock == cidr { + return true, nil + } + } + + return false, nil +} + // Create an ID for a route func routeIDHash(d *schema.ResourceData, r *ec2.Route) string { - return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCIDRBlock)) + return fmt.Sprintf("r-%s%d", d.Get("route_table_id").(string), hashcode.String(*r.DestinationCidrBlock)) } // Helper: retrieve a route @@ -260,7 +304,7 @@ func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, er routeTableID := rtbid findOpts := &ec2.DescribeRouteTablesInput{ - RouteTableIDs: []*string{&routeTableID}, + RouteTableIds: []*string{&routeTableID}, } resp, err := conn.DescribeRouteTables(findOpts) @@ -269,10 +313,25 @@ func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, er } for _, route := range (*resp.RouteTables[0]).Routes { - if *route.DestinationCIDRBlock == cidr { + if *route.DestinationCidrBlock == cidr { return route, nil } } return nil, nil } + +func deleteAwsRoute(conn *ec2.EC2, routeTableId string, cidr string) error { + deleteOpts := &ec2.DeleteRouteInput{ + RouteTableId: aws.String(routeTableId), + DestinationCidrBlock: aws.String(cidr), + } + log.Printf("[DEBUG] Route delete opts: %s", awsutil.Prettify(deleteOpts)) + + resp, err := conn.DeleteRoute(deleteOpts) + log.Printf("[DEBUG] Route delete result: %s", awsutil.Prettify(resp)) + if err != nil { + return err + } + return nil +} diff --git a/builtin/providers/aws/resource_aws_route_table.go b/builtin/providers/aws/resource_aws_route_table.go index 5f5660f19..162fa7243 100644 --- a/builtin/providers/aws/resource_aws_route_table.go +++ b/builtin/providers/aws/resource_aws_route_table.go @@ -41,6 +41,7 @@ func resourceAwsRouteTable() *schema.Resource { "route": &schema.Schema{ Type: schema.TypeSet, + Computed: true, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/builtin/providers/aws/resource_aws_route_test.go b/builtin/providers/aws/resource_aws_route_test.go index 39841a6bb..2e59e35aa 100644 --- a/builtin/providers/aws/resource_aws_route_test.go +++ b/builtin/providers/aws/resource_aws_route_test.go @@ -12,22 +12,172 @@ import ( func TestAccAWSRoute_basic(t *testing.T) { var route ec2.Route + //aws creates a default route + testCheck := func(s *terraform.State) error { + if *route.DestinationCidrBlock != "10.3.0.0/16" { + return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.3.0.0/16", *route.DestinationCidrBlock) + } + + name := "aws_internet_gateway.foo" + gwres, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s\n", name) + } + + if *route.GatewayId != gwres.Primary.ID { + return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) + } + + return nil + } + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRouteDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAWSRouteConfig, + Config: testAccAWSRouteBasicConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSRouteExists("aws_route.bar", &route), - testAccCheckAWSRouteAttributes(&route), + testCheck, ), }, }, }) } +func TestAccAWSRoute_changeCidr(t *testing.T) { + var route ec2.Route + var routeTable ec2.RouteTable + + //aws creates a default route + testCheck := func(s *terraform.State) error { + if *route.DestinationCidrBlock != "10.3.0.0/16" { + return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.3.0.0/16", *route.DestinationCidrBlock) + } + + name := "aws_internet_gateway.foo" + gwres, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s\n", name) + } + + if *route.GatewayId != gwres.Primary.ID { + return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) + } + + return nil + } + + testCheckChange := func(s *terraform.State) error { + if *route.DestinationCidrBlock != "10.2.0.0/16" { + return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "10.2.0.0/16", *route.DestinationCidrBlock) + } + + name := "aws_internet_gateway.foo" + gwres, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s\n", name) + } + + if *route.GatewayId != gwres.Primary.ID { + return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) + } + + if rtlen := len(routeTable.Routes); rtlen != 2 { + return fmt.Errorf("Route Table has too many routes (Expected=%d, Actual=%d)\n", rtlen, 2) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(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), + testAccCheckRouteTableExists("aws_route_table.foo", &routeTable), + testCheckChange, + ), + }, + }, + }) +} + +// Acceptance test if mixed inline and external routes are implemented +/* +func TestAccAWSRoute_mix(t *testing.T) { + var rt ec2.RouteTable + var route ec2.Route + + //aws creates a default route + testCheck := func(s *terraform.State) error { + if *route.DestinationCidrBlock != "0.0.0.0/0" { + return fmt.Errorf("Destination Cidr (Expected=%s, Actual=%s)\n", "0.0.0.0/0", *route.DestinationCidrBlock) + } + + name := "aws_internet_gateway.foo" + gwres, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s\n", name) + } + + if *route.GatewayId != gwres.Primary.ID { + return fmt.Errorf("Internet Gateway Id (Expected=%s, Actual=%s)\n", gwres.Primary.ID, *route.GatewayId) + } + + if len(rt.Routes) != 3 { + return fmt.Errorf("bad routes: %#v", rt.Routes) + } + + routes := make(map[string]*ec2.Route) + for _, r := range rt.Routes { + routes[*r.DestinationCidrBlock] = r + } + + if _, ok := routes["10.1.0.0/16"]; !ok { + return fmt.Errorf("Missing route %s: %#v", "10.1.0.0/16", rt.Routes) + } + if _, ok := routes["10.2.0.0/16"]; !ok { + return fmt.Errorf("Missing route %s: %#v", "10.2.0.0/16", rt.Routes) + } + if _, ok := routes["0.0.0.0/0"]; !ok { + return fmt.Errorf("Missing route %s: %#v", "0.0.0.0/0", rt.Routes) + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSRouteMixConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRouteTableExists("aws_route_table.foo", &rt), + testAccCheckAWSRouteExists("aws_route.bar", &route), + testCheck, + ), + }, + }, + }) +} +*/ + func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -42,7 +192,7 @@ func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc conn := testAccProvider.Meta().(*AWSClient).ec2conn r, err := findResourceRoute( conn, - rs.Primary.ID, + rs.Primary.Attributes["route_table_id"], rs.Primary.Attributes["destination_cidr_block"], ) @@ -54,21 +204,7 @@ func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc return fmt.Errorf("Route not found") } - return nil - } -} - -func testAccCheckAWSRouteAttributes(route *ec2.Route) resource.TestCheckFunc { - return func(s *terraform.State) error { - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_route" { - continue - } - if *route.DestinationCIDRBlock != rs.Primary.Attributes["destination_cidr_block"] { - return fmt.Errorf("Bad Destination CIDR Block on Route\n\t expected: %s\n\tgot %s\n", rs.Primary.Attributes["destination_cidr_block"], *route.DestinationCIDRBlock) - } - } + *res = *r return nil } @@ -83,7 +219,7 @@ func testAccCheckAWSRouteDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn route, err := findResourceRoute( conn, - rs.Primary.ID, + rs.Primary.Attributes["route_table_id"], rs.Primary.Attributes["destination_cidr_block"], ) @@ -95,7 +231,7 @@ func testAccCheckAWSRouteDestroy(s *terraform.State) error { return nil } -var testAccAWSRouteConfig = fmt.Sprint(` +var testAccAWSRouteBasicConfig = fmt.Sprint(` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" } @@ -106,15 +242,57 @@ resource "aws_internet_gateway" "foo" { resource "aws_route_table" "foo" { vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_route" "bar" { + route_table_id = "${aws_route_table.foo.id}" + destination_cidr_block = "10.3.0.0/16" + gateway_id = "${aws_internet_gateway.foo.id}" +} +`) + +var testAccAWSRouteBasicConfigChangeCidr = fmt.Sprint(` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_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_cidr_block = "10.2.0.0/16" + gateway_id = "${aws_internet_gateway.foo.id}" +} +`) + +// Acceptance test if mixed inline and external routes are implemented +var testAccAWSRouteMixConfig = fmt.Sprint(` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_internet_gateway" "foo" { + vpc_id = "${aws_vpc.foo.id}" +} + +resource "aws_route_table" "foo" { + vpc_id = "${aws_vpc.foo.id}" + route { cidr_block = "10.2.0.0/16" gateway_id = "${aws_internet_gateway.foo.id}" } } -resource "aws_route" "foo" { +resource "aws_route" "bar" { route_table_id = "${aws_route_table.foo.id}" - destination_cidr_block = "${aws_vpc.foo.cidr_block}" + destination_cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.foo.id}" } `) diff --git a/website/source/docs/providers/aws/r/route.html.markdown b/website/source/docs/providers/aws/r/route.html.markdown index 42ae71be1..0d359dd30 100644 --- a/website/source/docs/providers/aws/r/route.html.markdown +++ b/website/source/docs/providers/aws/r/route.html.markdown @@ -10,6 +10,12 @@ description: |- Provides a resource to create a routing table entry (a route) in a VPC routing table. +~> **NOTE on Route Tables and Routes:** Terraform currently +provides both a standalone [Route resource](route.html) and a Route Table resource with routes +defined in-line. At this time you cannot use a Route Table with in-line routes +in conjunction with any Route resources. Doing so will cause +a conflict of rule settings and will overwrite rules. + ## Example usage: ``` diff --git a/website/source/docs/providers/aws/r/route_table.html.markdown b/website/source/docs/providers/aws/r/route_table.html.markdown index e75025afa..e751b7193 100644 --- a/website/source/docs/providers/aws/r/route_table.html.markdown +++ b/website/source/docs/providers/aws/r/route_table.html.markdown @@ -10,6 +10,12 @@ description: |- Provides a resource to create a VPC routing table. +~> **NOTE on Route Tables and Routes:** Terraform currently +provides both a standalone [Route resource](route.html) and a Route Table resource with routes +defined in-line. At this time you cannot use a Route Table with in-line routes +in conjunction with any Route resources. Doing so will cause +a conflict of rule settings and will overwrite rules. + ## Example usage with tags: ```