From ac0afad6e99a00cd19c30952629bea7cde8704aa Mon Sep 17 00:00:00 2001 From: gkze Date: Tue, 23 Jun 2015 20:44:02 -0700 Subject: [PATCH 1/5] Add aws_route resource --- builtin/providers/aws/provider.go | 1 + builtin/providers/aws/resource_aws_route.go | 278 ++++++++++++++++++ .../providers/aws/resource_aws_route_test.go | 120 ++++++++ .../docs/providers/aws/r/route.html.markdown | 51 ++++ .../providers/aws/r/route_table.html.markdown | 2 + website/source/layouts/aws.erb | 4 + 6 files changed, 456 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_route.go create mode 100644 builtin/providers/aws/resource_aws_route_test.go create mode 100644 website/source/docs/providers/aws/r/route.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index f73580d0f..8bcc4bf8a 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -231,6 +231,7 @@ func Provider() terraform.ResourceProvider { "aws_route53_zone_association": resourceAwsRoute53ZoneAssociation(), "aws_route53_zone": resourceAwsRoute53Zone(), "aws_route53_health_check": resourceAwsRoute53HealthCheck(), + "aws_route": resourceAwsRoute(), "aws_route_table": resourceAwsRouteTable(), "aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_s3_bucket": resourceAwsS3Bucket(), diff --git a/builtin/providers/aws/resource_aws_route.go b/builtin/providers/aws/resource_aws_route.go new file mode 100644 index 000000000..a85388c0e --- /dev/null +++ b/builtin/providers/aws/resource_aws_route.go @@ -0,0 +1,278 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +// AWS Route resource Schema delcaration +func resourceAwsRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsRouteCreate, + Read: resourceAwsRouteRead, + Update: resourceAwsRouteUpdate, + Delete: resourceAwsRouteDelete, + + Schema: map[string]*schema.Schema{ + "destination_cidr_block": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "destination_prefix_list_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "gateway_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "instance_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "instance_owner_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "network_interface_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "origin": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "state": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "route_table_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "vpc_peering_connection_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + var numTargets int + var setTarget string + allowedTargets := []string{ + "gateway_id", + "instance_id", + "network_interface_id", + "vpc_peering_connection_id", + } + createOpts := &ec2.CreateRouteInput{} + + // Check if more than 1 target is specified + for _, target := range allowedTargets { + if len(d.Get(target).(string)) > 0 { + numTargets++ + setTarget = target + } + } + + if numTargets > 1 { + fmt.Errorf("Error: more than 1 target specified. Only 1 of gateway_id" + + "instance_id, network_interface_id, route_table_id or" + + "vpc_peering_connection_id is allowed.") + } + + // 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)), + } + 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)), + } + 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)), + } + 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)), + } + default: + fmt.Errorf("Error: invalid target type specified.") + } + log.Printf("[DEBUG] Route create config: %s", awsutil.Prettify(createOpts)) + + // Create the route + _, err := conn.CreateRoute(createOpts) + if err != nil { + return fmt.Errorf("Error creating route: %s", err) + } + + route, err := findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) + if err != nil { + fmt.Errorf("Error: %s", awsutil.Prettify(err)) + } + + d.SetId(routeIDHash(d, route)) + + return resourceAwsRouteRead(d, meta) +} + +func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + route, err := findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) + if err != nil { + 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("origin", route.Origin) + d.Set("state", route.State) + d.Set("vpc_peering_connection_id", route.VPCPeeringConnectionID) + + return nil +} + +func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + var numTargets int + var setTarget string + allowedTargets := []string{ + "gateway_id", + "instance_id", + "network_interface_id", + "vpc_peering_connection_id", + } + replaceOpts := &ec2.ReplaceRouteInput{} + + // Check if more than 1 target is specified + for _, target := range allowedTargets { + if len(d.Get(target).(string)) > 0 { + numTargets++ + setTarget = target + } + } + + if numTargets > 1 { + fmt.Errorf("Error: more than 1 target specified. Only 1 of gateway_id" + + "instance_id, network_interface_id, route_table_id or" + + "vpc_peering_connection_id is allowed.") + } + + // Formulate ReplaceRouteInput based on the target type + 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)), + } + 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)), + } + 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)), + } + 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)), + } + default: + fmt.Errorf("Error: invalid target type specified.") + } + log.Printf("[DEBUG] Route replace config: %s", awsutil.Prettify(replaceOpts)) + + // Replace the route + _, err := conn.ReplaceRoute(replaceOpts) + if err != nil { + return err + } + + return nil +} + +func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { + 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)) + if err != nil { + return err + } + + d.SetId("") + + return 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)) +} + +// Helper: retrieve a route +func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, error) { + routeTableID := rtbid + + findOpts := &ec2.DescribeRouteTablesInput{ + RouteTableIDs: []*string{&routeTableID}, + } + + resp, err := conn.DescribeRouteTables(findOpts) + if err != nil { + return nil, err + } + + for _, route := range (*resp.RouteTables[0]).Routes { + if *route.DestinationCIDRBlock == cidr { + return route, nil + } + } + + return nil, nil +} diff --git a/builtin/providers/aws/resource_aws_route_test.go b/builtin/providers/aws/resource_aws_route_test.go new file mode 100644 index 000000000..39841a6bb --- /dev/null +++ b/builtin/providers/aws/resource_aws_route_test.go @@ -0,0 +1,120 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSRoute_basic(t *testing.T) { + var route ec2.Route + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSRouteConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSRouteExists("aws_route.bar", &route), + testAccCheckAWSRouteAttributes(&route), + ), + }, + }, + }) +} + +func testAccCheckAWSRouteExists(n string, res *ec2.Route) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s\n", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + r, err := findResourceRoute( + conn, + rs.Primary.ID, + rs.Primary.Attributes["destination_cidr_block"], + ) + + if err != nil { + return err + } + + if r == nil { + 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) + } + } + + return nil + } +} + +func testAccCheckAWSRouteDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_route" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + route, err := findResourceRoute( + conn, + rs.Primary.ID, + rs.Primary.Attributes["destination_cidr_block"], + ) + + if route == nil && err == nil { + return nil + } + } + + return nil +} + +var testAccAWSRouteConfig = 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" { + route_table_id = "${aws_route_table.foo.id}" + destination_cidr_block = "${aws_vpc.foo.cidr_block}" + 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 new file mode 100644 index 000000000..42ae71be1 --- /dev/null +++ b/website/source/docs/providers/aws/r/route.html.markdown @@ -0,0 +1,51 @@ +--- +layout: "aws" +page_title: "AWS: aws_route_table" +sidebar_current: "docs-aws-resource-route-table|" +description: |- + Provides a resource to create a routing entry in a VPC routing table. +--- + +# aws\_route + +Provides a resource to create a routing table entry (a route) in a VPC routing table. + +## Example usage: + +``` +resource "aws_route" "r" { + route_table_id = "rtb-4fbb3ac4" + destination_cidr_block = "10.0.1.0/22" + vpc_peering_connection_id = "pcx-45ff3dc1" + depends_on = ["aws_route_table.testing"] +} +``` + +## 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. +* `vpc_peering_connection_id` - (Optional) An ID of a VPC peering connection. +* `gateway_id` - (Optional) An ID of a VPC internet gateway or a virtual private gateway. +* `instance_id` - (Optional) An ID of a NAT instance. +* `network_interface_id` - (Optional) An ID of a network interface. + +Each route must contain either a `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. + +## Attributes Reference + +The following attributes are exported: + +~> **NOTE:** Only the target type that is specified (one of the above) +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. +* `vpc_peering_connection_id` - An ID of a VPC peering connection. +* `gateway_id` - An ID of a VPC internet gateway or a virtual private gateway. +* `instance_id` - An ID of a NAT instance. +* `network_interface_id` - An ID of a network interface. 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 1bbeb45c3..e75025afa 100644 --- a/website/source/docs/providers/aws/r/route_table.html.markdown +++ b/website/source/docs/providers/aws/r/route_table.html.markdown @@ -50,5 +50,7 @@ is created implicitly and cannot be specified. ## Attributes Reference The following attributes are exported: +~> **NOTE:** Only the target that is entered is exported as a readable +attribute once the route resource is created. * `id` - The ID of the routing table diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 4b34da23a..da61af0d3 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -401,6 +401,10 @@ aws_route53_zone_association + > + aws_route + + From 6a593f9d1778a607c3b1400799e3d6e6c7592ee8 Mon Sep 17 00:00:00 2001 From: BSick7 Date: Thu, 15 Oct 2015 17:55:47 -0400 Subject: [PATCH 2/5] Fixing aws identifiers for aws_route. Fixing basic acceptance test. Adding warning to website about mixed mode. Adding exists to aws_route. Adding acceptance test for changing destination_cidr_block. --- builtin/providers/aws/resource_aws_route.go | 147 ++++++++---- .../providers/aws/resource_aws_route_table.go | 1 + .../providers/aws/resource_aws_route_test.go | 222 ++++++++++++++++-- .../docs/providers/aws/r/route.html.markdown | 6 + .../providers/aws/r/route_table.html.markdown | 6 + 5 files changed, 316 insertions(+), 66 deletions(-) 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: ``` From 9cb1aada0a1d7fcfc446d9725048eec00b75d300 Mon Sep 17 00:00:00 2001 From: BSick7 Date: Mon, 26 Oct 2015 13:26:47 -0400 Subject: [PATCH 3/5] Fixing page metadata for aws_route. --- website/source/docs/providers/aws/r/route.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/providers/aws/r/route.html.markdown b/website/source/docs/providers/aws/r/route.html.markdown index 0d359dd30..3606555e6 100644 --- a/website/source/docs/providers/aws/r/route.html.markdown +++ b/website/source/docs/providers/aws/r/route.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" -page_title: "AWS: aws_route_table" -sidebar_current: "docs-aws-resource-route-table|" +page_title: "AWS: aws_route" +sidebar_current: "docs-aws-resource-route|" description: |- Provides a resource to create a routing entry in a VPC routing table. --- From e4465adca5ab8e7210058dae56d15cef70621190 Mon Sep 17 00:00:00 2001 From: BSick7 Date: Mon, 26 Oct 2015 13:38:17 -0400 Subject: [PATCH 4/5] Removing usage of awsutil.Prettify. --- builtin/providers/aws/resource_aws_route.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route.go b/builtin/providers/aws/resource_aws_route.go index 62d81a215..a489d885c 100644 --- a/builtin/providers/aws/resource_aws_route.go +++ b/builtin/providers/aws/resource_aws_route.go @@ -5,7 +5,6 @@ import ( "log" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" @@ -129,7 +128,7 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { default: fmt.Errorf("Error: invalid target type specified.") } - log.Printf("[DEBUG] Route create config: %s", awsutil.Prettify(createOpts)) + log.Printf("[DEBUG] Route create config: %s", createOpts) // Create the route _, err := conn.CreateRoute(createOpts) @@ -139,7 +138,7 @@ func resourceAwsRouteCreate(d *schema.ResourceData, meta interface{}) error { route, err := findResourceRoute(conn, d.Get("route_table_id").(string), d.Get("destination_cidr_block").(string)) if err != nil { - fmt.Errorf("Error: %s", awsutil.Prettify(err)) + fmt.Errorf("Error: %s", err) } d.SetId(routeIDHash(d, route)) @@ -227,7 +226,7 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { default: fmt.Errorf("Error: invalid target type specified.") } - log.Printf("[DEBUG] Route replace config: %s", awsutil.Prettify(replaceOpts)) + log.Printf("[DEBUG] Route replace config: %s", replaceOpts) // Replace the route _, err := conn.ReplaceRoute(replaceOpts) @@ -326,10 +325,10 @@ func deleteAwsRoute(conn *ec2.EC2, routeTableId string, cidr string) error { RouteTableId: aws.String(routeTableId), DestinationCidrBlock: aws.String(cidr), } - log.Printf("[DEBUG] Route delete opts: %s", awsutil.Prettify(deleteOpts)) + log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) resp, err := conn.DeleteRoute(deleteOpts) - log.Printf("[DEBUG] Route delete result: %s", awsutil.Prettify(resp)) + log.Printf("[DEBUG] Route delete result: %s", resp) if err != nil { return err } From e0aad68ef1af0c169603862167779dab4a908a4d Mon Sep 17 00:00:00 2001 From: BSick7 Date: Mon, 26 Oct 2015 13:45:21 -0400 Subject: [PATCH 5/5] Relying on `ForceNew` for `destination_cidr_block` since it is part of the unique id instead of manually recreating the resource. --- builtin/providers/aws/resource_aws_route.go | 48 ++++----------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route.go b/builtin/providers/aws/resource_aws_route.go index a489d885c..96f731484 100644 --- a/builtin/providers/aws/resource_aws_route.go +++ b/builtin/providers/aws/resource_aws_route.go @@ -23,6 +23,7 @@ func resourceAwsRoute() *schema.Resource { "destination_cidr_block": &schema.Schema{ Type: schema.TypeString, Required: true, + ForceNew: true, }, "destination_prefix_list_id": &schema.Schema{ @@ -166,10 +167,6 @@ func resourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { } 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 @@ -237,31 +234,17 @@ func resourceAwsRouteUpdate(d *schema.ResourceData, meta interface{}) error { return nil } -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 +func resourceAwsRouteDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn - 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") + deleteOpts := &ec2.DeleteRouteInput{ + RouteTableId: aws.String(d.Get("route_table_id").(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), } + log.Printf("[DEBUG] Route delete opts: %s", deleteOpts) - 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)) + resp, err := conn.DeleteRoute(deleteOpts) + log.Printf("[DEBUG] Route delete result: %s", resp) if err != nil { return err } @@ -319,18 +302,3 @@ func findResourceRoute(conn *ec2.EC2, rtbid string, cidr string) (*ec2.Route, er 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", deleteOpts) - - resp, err := conn.DeleteRoute(deleteOpts) - log.Printf("[DEBUG] Route delete result: %s", resp) - if err != nil { - return err - } - return nil -}