diff --git a/aws/data_source_aws_route.go b/aws/data_source_aws_route.go new file mode 100644 index 00000000000..ea7eb6d24ba --- /dev/null +++ b/aws/data_source_aws_route.go @@ -0,0 +1,180 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsRoute() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsRouteRead, + + Schema: map[string]*schema.Schema{ + "route_table_id": { + Type: schema.TypeString, + Required: true, + }, + "destination_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "destination_ipv6_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "egress_only_gateway_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "gateway_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "instance_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "nat_gateway_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vpc_peering_connection_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceAwsRouteRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + req := &ec2.DescribeRouteTablesInput{} + rtbId := d.Get("route_table_id") + cidr := d.Get("destination_cidr_block") + ipv6Cidr := d.Get("destination_ipv6_cidr_block") + + req.Filters = buildEC2AttributeFilterList( + map[string]string{ + "route-table-id": rtbId.(string), + "route.destination-cidr-block": cidr.(string), + "route.destination-ipv6-cidr-block": ipv6Cidr.(string), + }, + ) + + log.Printf("[DEBUG] Reading Route Table: %s", req) + resp, err := conn.DescribeRouteTables(req) + if err != nil { + return err + } + if resp == nil || len(resp.RouteTables) == 0 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + if len(resp.RouteTables) > 1 { + return fmt.Errorf("Your query returned more than one route table. Please change your search criteria and try again.") + } + + results := getRoutes(resp.RouteTables[0], d) + + if len(results) == 0 { + return fmt.Errorf("No routes matching supplied arguments found in table(s)") + } + if len(results) > 1 { + return fmt.Errorf("Multiple routes matched; use additional constraints to reduce matches to a single route") + } + route := results[0] + + d.SetId(routeIDHash(d, route)) // using function from "resource_aws_route.go" + d.Set("destination_cidr_block", route.DestinationCidrBlock) + d.Set("destination_ipv6_cidr_block", route.DestinationIpv6CidrBlock) + d.Set("egress_only_gateway_id", route.EgressOnlyInternetGatewayId) + d.Set("gateway_id", route.GatewayId) + d.Set("instance_id", route.InstanceId) + d.Set("nat_gateway_id", route.NatGatewayId) + d.Set("vpc_peering_connection_id", route.VpcPeeringConnectionId) + d.Set("network_interface_id", route.NetworkInterfaceId) + + return nil +} + +func getRoutes(table *ec2.RouteTable, d *schema.ResourceData) []*ec2.Route { + ec2Routes := table.Routes + routes := make([]*ec2.Route, 0, len(ec2Routes)) + // Loop through the routes and add them to the set + for _, r := range ec2Routes { + + if r.Origin != nil && *r.Origin == "EnableVgwRoutePropagation" { + continue + } + + if r.DestinationPrefixListId != nil { + // Skipping because VPC endpoint routes are handled separately + // See aws_vpc_endpoint + continue + } + + if v, ok := d.GetOk("destination_cidr_block"); ok { + if r.DestinationCidrBlock == nil || *r.DestinationCidrBlock != v.(string) { + continue + } + } + + if v, ok := d.GetOk("destination_ipv6_cidr_block"); ok { + if r.DestinationIpv6CidrBlock == nil || *r.DestinationIpv6CidrBlock != v.(string) { + continue + } + } + + if v, ok := d.GetOk("egress_only_gateway_id"); ok { + if r.EgressOnlyInternetGatewayId == nil || *r.EgressOnlyInternetGatewayId != v.(string) { + continue + } + } + + if v, ok := d.GetOk("gateway_id"); ok { + if r.GatewayId == nil || *r.GatewayId != v.(string) { + continue + } + } + + if v, ok := d.GetOk("instance_id"); ok { + if r.InstanceId == nil || *r.InstanceId != v.(string) { + continue + } + } + + if v, ok := d.GetOk("nat_gateway_id"); ok { + if r.NatGatewayId == nil || *r.NatGatewayId != v.(string) { + continue + } + } + + if v, ok := d.GetOk("vpc_peering_connection_id"); ok { + if r.VpcPeeringConnectionId == nil || *r.VpcPeeringConnectionId != v.(string) { + continue + } + } + + if v, ok := d.GetOk("network_interface_id"); ok { + if r.NetworkInterfaceId == nil || *r.NetworkInterfaceId != v.(string) { + continue + } + } + routes = append(routes, r) + } + return routes +} diff --git a/aws/data_source_aws_route_test.go b/aws/data_source_aws_route_test.go new file mode 100644 index 00000000000..ecd69f9a1ce --- /dev/null +++ b/aws/data_source_aws_route_test.go @@ -0,0 +1,174 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceAwsRoute_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAwsRouteGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsRouteCheck("data.aws_route.by_destination_cidr_block"), + testAccDataSourceAwsRouteCheck("data.aws_route.by_instance_id"), + testAccDataSourceAwsRouteCheck("data.aws_route.by_peering_connection_id"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccDataSourceAwsRouteCheck(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + + if !ok { + return fmt.Errorf("root module has no resource called %s", name) + } + + r, ok := s.RootModule().Resources["aws_route.test"] + if !ok { + return fmt.Errorf("can't find aws_route.test in state") + } + rts, ok := s.RootModule().Resources["aws_route_table.test"] + if !ok { + return fmt.Errorf("can't find aws_route_table.test in state") + } + + attr := rs.Primary.Attributes + + if attr["route_table_id"] != r.Primary.Attributes["route_table_id"] { + return fmt.Errorf( + "route_table_id is %s; want %s", + attr["route_table_id"], + r.Primary.Attributes["route_table_id"], + ) + } + + if attr["route_table_id"] != rts.Primary.Attributes["id"] { + return fmt.Errorf( + "route_table_id is %s; want %s", + attr["route_table_id"], + rts.Primary.Attributes["id"], + ) + } + + return nil + } +} + +const testAccDataSourceAwsRouteGroupConfig = ` +provider "aws" { + region = "ap-southeast-2" +} + +resource "aws_vpc" "test" { + cidr_block = "172.16.0.0/16" + + tags { + Name = "terraform-testacc-route-table-data-source" + } +} + +resource "aws_vpc" "dest" { + cidr_block = "172.17.0.0/16" + + tags { + Name = "terraform-testacc-route-table-data-source" + } +} + +resource "aws_vpc_peering_connection" "test" { + peer_vpc_id = "${aws_vpc.dest.id}" + vpc_id = "${aws_vpc.test.id}" + auto_accept = true +} + +resource "aws_subnet" "test" { + cidr_block = "172.16.0.0/24" + vpc_id = "${aws_vpc.test.id}" + tags { + Name = "tf-acc-route-table-data-source" + } +} + +resource "aws_route_table" "test" { + vpc_id = "${aws_vpc.test.id}" + tags { + Name = "terraform-testacc-routetable-data-source" + } +} + +resource "aws_route" "pcx" { + route_table_id = "${aws_route_table.test.id}" + vpc_peering_connection_id = "${aws_vpc_peering_connection.test.id}" + destination_cidr_block = "10.0.2.0/24" +} + +resource "aws_route_table_association" "a" { + subnet_id = "${aws_subnet.test.id}" + route_table_id = "${aws_route_table.test.id}" +} + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] # Canonical + } + + resource "aws_instance" "web" { + ami = "${data.aws_ami.ubuntu.id}" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.test.id}" + tags { + Name = "HelloWorld" + } + } + + +resource "aws_route" "test" { + route_table_id = "${aws_route_table.test.id}" + destination_cidr_block = "10.0.1.0/24" + instance_id = "${aws_instance.web.id}" + timeouts { + create ="5m" + } +} + +data "aws_route" "by_peering_connection_id"{ + route_table_id = "${aws_route_table.test.id}" + vpc_peering_connection_id = "${aws_route.pcx.vpc_peering_connection_id}" +} + +data "aws_route" "by_destination_cidr_block"{ + route_table_id = "${aws_route_table.test.id}" + destination_cidr_block = "10.0.1.0/24" + depends_on = ["aws_route.test"] +} + +data "aws_route" "by_instance_id"{ + route_table_id = "${aws_route_table.test.id}" + instance_id = "${aws_instance.web.id}" + depends_on = ["aws_route.test"] +} + + +` diff --git a/aws/provider.go b/aws/provider.go index caa4620de62..aaa8fa57efb 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -226,6 +226,7 @@ func Provider() terraform.ResourceProvider { "aws_redshift_cluster": dataSourceAwsRedshiftCluster(), "aws_redshift_service_account": dataSourceAwsRedshiftServiceAccount(), "aws_region": dataSourceAwsRegion(), + "aws_route": dataSourceAwsRoute(), "aws_route_table": dataSourceAwsRouteTable(), "aws_route53_zone": dataSourceAwsRoute53Zone(), "aws_s3_bucket": dataSourceAwsS3Bucket(), diff --git a/website/aws.erb b/website/aws.erb index 91f132be189..ccee17dc00e 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -247,6 +247,9 @@ > aws_route_table + > + aws_route + > aws_s3_bucket diff --git a/website/docs/d/route.html.markdown b/website/docs/d/route.html.markdown new file mode 100644 index 00000000000..0498729048f --- /dev/null +++ b/website/docs/d/route.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "aws" +page_title: "AWS: aws_route" +sidebar_current: "docs-aws-datasource-route" +description: |- + Provides details about a specific Route +--- + +# Data Source: aws_route + +`aws_route` provides details about a specific Route. + +This resource can prove useful when finding the resource +associated with a CIDR. For example, finding the peering +connection associated with a CIDR value. + +## Example Usage + +The following example shows how one might use a CIDR value to find a network interface id +and use this to create a data source of that network interface. + +```hcl +variable "subnet_id" {} + + +data "aws_route_table" "selected" { + subnet_id = "${var.subnet_id}" +} + +data "aws_route" "route"{ + route_table_id = "${aws_route_table.selected.id}" + destination_cidr_block = "10.0.1.0/24" +} + +data "aws_network_interface" "interface" { + network_interface_id = "${data.aws_route.route.network_interface_id}" +} +``` + +## Argument Reference + +The arguments of this data source act as filters for querying the available +Route in the current region. The given filters must match exactly one +Route whose data will be exported as attributes. + +* `route_table_id` - (Required) The id of the specific Route Table containing the Route entry. + +* `destination_cidr_block` - (Optional) The CIDR block of the Route belonging to the Route Table. + +* `destination_ipv6_cidr_block` - (Optional) The IPv6 CIDR block of the Route belonging to the Route Table. + +* `egress_only_gateway_id` - (Optional) The Egress Only Gateway ID of the Route belonging to the Route Table. + +* `gateway_id` - (Optional) The Gateway ID of the Route belonging to the Route Table. + +* `instance_id` - (Optional) The Instance ID of the Route belonging to the Route Table. + +* `nat_gateway_id` - (Optional) The NAT Gateway ID of the Route belonging to the Route Table. + +* `vpc_peering_connection_id` - (Optional) The VPC Peering Connection ID of the Route belonging to the Route Table. + +* `network_interface_id` - (Optional) The Network Interface ID of the Route belonging to the Route Table. + +## Attributes Reference + +All of the argument attributes are also exported as +result attributes when there is data available. For example, the `vpc_peering_connection_id` field will be empty when the route is attached to a Network Interface. \ No newline at end of file