diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index b8ee626e5c5f..611d734febc5 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -109,6 +109,7 @@ func Provider() terraform.ResourceProvider { "aws_vpc_dhcp_options": resourceAwsVpcDhcpOptions(), "aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(), "aws_vpn_connection": resourceAwsVpnConnection(), + "aws_vpn_connection_route": resourceAwsVpnConnectionRoute(), "aws_vpn_gateway": resourceAwsVpnGateway(), }, diff --git a/builtin/providers/aws/resource_vpn_connection_route.go b/builtin/providers/aws/resource_vpn_connection_route.go new file mode 100644 index 000000000000..863a891e7a6a --- /dev/null +++ b/builtin/providers/aws/resource_vpn_connection_route.go @@ -0,0 +1,135 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsVpnConnectionRoute() *schema.Resource { + return &schema.Resource{ + // You can't update a route. You can just delete one and make + // a new one. + Create: resourceAwsVpnConnectionRouteCreate, + Update: resourceAwsVpnConnectionRouteCreate, + + Read: resourceAwsVpnConnectionRouteRead, + Delete: resourceAwsVpnConnectionRouteDelete, + + Schema: map[string]*schema.Schema{ + "destination_cidr_block": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vpn_connection_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsVpnConnectionRouteCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + createOpts := &ec2.CreateVPNConnectionRouteInput{ + DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), + VPNConnectionID: aws.String(d.Get("vpn_connection_id").(string)), + } + + // Create the route. + log.Printf("[DEBUG] Creating VPN connection route") + _, err := conn.CreateVPNConnectionRoute(createOpts) + if err != nil { + return fmt.Errorf("Error creating VPN connection route: %s", err) + } + + // Store the ID by the only two data we have available to us. + d.SetId(fmt.Sprintf("%s:%s", *createOpts.DestinationCIDRBlock, *createOpts.VPNConnectionID)) + + return resourceAwsVpnConnectionRouteRead(d, meta) +} + +func resourceAwsVpnConnectionRouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + cidrBlock, vpnConnectionId := resourceAwsVpnConnectionRouteParseId(d.Id()) + + routeFilters := []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("route.destination-cidr-block"), + Values: []*string{aws.String(cidrBlock)}, + }, + &ec2.Filter{ + Name: aws.String("vpn-connection-id"), + Values: []*string{aws.String(vpnConnectionId)}, + }, + } + + // Technically, we know everything there is to know about the route + // from its ID, but we still want to catch cases where it changes + // outside of terraform and results in a stale state file. Hence, + // conduct a read. + resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{ + Filters: routeFilters, + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" { + d.SetId("") + return nil + } else { + log.Printf("[ERROR] Error finding VPN connection route: %s", err) + return err + } + } + + vpnConnection := resp.VPNConnections[0] + + var found bool + for _, r := range vpnConnection.Routes { + if *r.DestinationCIDRBlock == cidrBlock { + d.Set("destination_cidr_block", *r.DestinationCIDRBlock) + d.Set("vpn_connection_id", *vpnConnection.VPNConnectionID) + found = true + } + } + if !found { + // Something other than terraform eliminated the route. + d.SetId("") + } + + return nil +} + +func resourceAwsVpnConnectionRouteDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + _, err := conn.DeleteVPNConnectionRoute(&ec2.DeleteVPNConnectionRouteInput{ + DestinationCIDRBlock: aws.String(d.Get("destination_cidr_block").(string)), + VPNConnectionID: aws.String(d.Get("vpn_connection_id").(string)), + }) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" { + d.SetId("") + return nil + } else { + log.Printf("[ERROR] Error deleting VPN connection route: %s", err) + return err + } + } + + return nil +} + +func resourceAwsVpnConnectionRouteParseId(id string) (string, string) { + parts := strings.SplitN(id, ":", 2) + return parts[0], parts[1] +} diff --git a/builtin/providers/aws/resource_vpn_connection_route_test.go b/builtin/providers/aws/resource_vpn_connection_route_test.go new file mode 100644 index 000000000000..55779898c094 --- /dev/null +++ b/builtin/providers/aws/resource_vpn_connection_route_test.go @@ -0,0 +1,150 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAwsVpnConnectionRoute(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccAwsVpnConnectionRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAwsVpnConnectionRouteConfig, + Check: resource.ComposeTestCheckFunc( + testAccAwsVpnConnectionRoute( + "aws_vpn_gateway.vpn_gateway", + "aws_customer_gateway.customer_gateway", + "aws_vpn_connection.vpn_connection", + "aws_vpn_connection_route.foo", + ), + ), + }, + resource.TestStep{ + Config: testAccAwsVpnConnectionRouteConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccAwsVpnConnectionRoute( + "aws_vpn_gateway.vpn_gateway", + "aws_customer_gateway.customer_gateway", + "aws_vpn_connection.vpn_connection", + "aws_vpn_connection_route.foo", + ), + ), + }, + }, + }) +} + +func testAccAwsVpnConnectionRouteDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +func testAccAwsVpnConnectionRoute( + vpnGatewayResource string, + customerGatewayResource string, + vpnConnectionResource string, + vpnConnectionRouteResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[vpnConnectionRouteResource] + if !ok { + return fmt.Errorf("Not found: %s", vpnConnectionRouteResource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + route, ok := s.RootModule().Resources[vpnConnectionRouteResource] + if !ok { + return fmt.Errorf("Not found: %s", vpnConnectionRouteResource) + } + + cidrBlock, vpnConnectionId := resourceAwsVpnConnectionRouteParseId(route.Primary.ID) + + routeFilters := []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("route.destination-cidr-block"), + Values: []*string{aws.String(cidrBlock)}, + }, + &ec2.Filter{ + Name: aws.String("vpn-connection-id"), + Values: []*string{aws.String(vpnConnectionId)}, + }, + } + + ec2conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := ec2conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{ + Filters: routeFilters, + }) + if err != nil { + return err + } + + return nil + } +} + +const testAccAwsVpnConnectionRouteConfig = ` +resource "aws_vpn_gateway" "vpn_gateway" { + tags { + Name = "vpn_gateway" + } +} + +resource "aws_customer_gateway" "customer_gateway" { + bgp_asn = 60000 + ip_address = "182.0.0.1" + type = "ipsec.1" +} + +resource "aws_vpn_connection" "vpn_connection" { + vpn_gateway_id = "${aws_vpn_gateway.vpn_gateway.id}" + customer_gateway_id = "${aws_customer_gateway.customer_gateway.id}" + type = "ipsec.1" + static_routes_only = true +} + +resource "aws_vpn_connection_route" "foo" { + destination_cidr_block = "172.168.10.0/24" + vpn_connection_id = "${aws_vpn_connection.vpn_connection.id}" +} +` + +// Change destination_cidr_block +const testAccAwsVpnConnectionRouteConfigUpdate = ` +resource "aws_vpn_gateway" "vpn_gateway" { + tags { + Name = "vpn_gateway" + } +} + +resource "aws_customer_gateway" "customer_gateway" { + bgp_asn = 60000 + ip_address = "182.0.0.1" + type = "ipsec.1" +} + +resource "aws_vpn_connection" "vpn_connection" { + vpn_gateway_id = "${aws_vpn_gateway.vpn_gateway.id}" + customer_gateway_id = "${aws_customer_gateway.customer_gateway.id}" + type = "ipsec.1" + static_routes_only = true +} + +resource "aws_vpn_connection_route" "foo" { + destination_cidr_block = "172.168.20.0/24" + vpn_connection_id = "${aws_vpn_connection.vpn_connection.id}" +} +` diff --git a/website/source/docs/providers/aws/r/vpc_connection_route.html.markdown b/website/source/docs/providers/aws/r/vpc_connection_route.html.markdown new file mode 100644 index 000000000000..a0d2f2ccc14c --- /dev/null +++ b/website/source/docs/providers/aws/r/vpc_connection_route.html.markdown @@ -0,0 +1,55 @@ +--- +layout: "aws" +page_title: "AWS: aws_vpn_connection_route" +sidebar_current: "docs-aws-resource-vpn-connection-route" +description: |- + Provides a static route between a VPN connection and a customer gateway. +--- + +# aws\_vpn\_connection\_route + +Provides a static route between a VPN connection and a customer gateway. + +## Example Usage + +``` +resource "aws_vpc" "vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_vpn_gateway" "vpn_gateway" { + vpc_id = "${aws_vpc.vpc.id}" +} + +resource "aws_customer_gateway" "customer_gateway" { + bgp_asn = 60000 + ip_address = "172.0.0.1" + type = ipsec.1 +} + +resource "aws_vpn_connection" "main" { + vpn_gateway_id = "${aws_vpn_gateway.vpn_gateway.id}" + customer_gateway_id = "${aws_customer_gateway.customer_gateway.id}" + type = "ipsec.1" + static_routes_only = true +} + +resource "aws_vpn_connection_route" "office" { + destination_cidr_block = "192.168.10.0/24" + vpn_connection_id = "${aws_vpn_connection.main.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `destination_cidr_block` - (Required) The CIDR block associated with the local subnet of the customer network. +* `vpn_connection_id` - (Required) The ID of the VPN connection. + +## Attribute Reference + +The following attributes are exported: + +* `destination_cidr_block` - The CIDR block associated with the local subnet of the customer network. +* `vpn_connection_id` - The ID of the VPN connection. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 4e83e735d8a8..08345364927c 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -123,6 +123,10 @@ aws_vpn_connection +