diff --git a/builtin/providers/aws/data_source_aws_route53_zone.go b/builtin/providers/aws/data_source_aws_route53_zone.go new file mode 100644 index 000000000000..a4df2c754794 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_route53_zone.go @@ -0,0 +1,176 @@ +package aws + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/route53" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAwsRoute53Zone() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsRoute53ZoneRead, + + Schema: map[string]*schema.Schema{ + "zone_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "private_zone": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "caller_reference": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "vpc_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "tags": tagsSchemaComputed(), + "resource_record_set_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).r53conn + name, nameExists := d.GetOk("name") + name = hostedZoneName(name.(string)) + id, idExists := d.GetOk("zone_id") + vpcId, vpcIdExists := d.GetOk("vpc_id") + tags := tagsFromMap(d.Get("tags").(map[string]interface{})) + if nameExists && idExists { + return fmt.Errorf("zone_id and name arguments can't be used together") + } else if !nameExists && !idExists { + return fmt.Errorf("Either name or zone_id must be set") + } + + var nextMarker *string + + var hostedZoneFound *route53.HostedZone + // We loop through all hostedzone + for allHostedZoneListed := false; !allHostedZoneListed; { + req := &route53.ListHostedZonesInput{} + if nextMarker != nil { + req.Marker = nextMarker + } + resp, err := conn.ListHostedZones(req) + + if err != nil { + return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", err) + } + for _, hostedZone := range resp.HostedZones { + hostedZoneId := cleanZoneID(*hostedZone.Id) + if idExists && hostedZoneId == id.(string) { + hostedZoneFound = hostedZone + break + // we check if the name is the same as requested and if private zone field is the same as requested or if there is a vpc_id + } else if *hostedZone.Name == name && (*hostedZone.Config.PrivateZone == d.Get("private_zone").(bool) || (*hostedZone.Config.PrivateZone == true && vpcIdExists)) { + matchingVPC := false + if vpcIdExists { + reqHostedZone := &route53.GetHostedZoneInput{} + reqHostedZone.Id = aws.String(hostedZoneId) + + respHostedZone, errHostedZone := conn.GetHostedZone(reqHostedZone) + if errHostedZone != nil { + return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", errHostedZone) + } + // we go through all VPCs + for _, vpc := range respHostedZone.VPCs { + if *vpc.VPCId == vpcId.(string) { + matchingVPC = true + break + } + } + } else { + matchingVPC = true + } + // we check if tags match + matchingTags := true + if len(tags) > 0 { + reqListTags := &route53.ListTagsForResourceInput{} + reqListTags.ResourceId = aws.String(hostedZoneId) + reqListTags.ResourceType = aws.String("hostedzone") + respListTags, errListTags := conn.ListTagsForResource(reqListTags) + + if errListTags != nil { + return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", errListTags) + } + for _, tag := range tags { + found := false + for _, tagRequested := range respListTags.ResourceTagSet.Tags { + if *tag.Key == *tagRequested.Key && *tag.Value == *tagRequested.Value { + found = true + } + } + + if !found { + matchingTags = false + break + } + } + + } + + if matchingTags && matchingVPC { + if hostedZoneFound != nil { + return fmt.Errorf("multplie Route53Zone found please use vpc_id option to filter") + } else { + hostedZoneFound = hostedZone + } + } + } + + } + if *resp.IsTruncated { + + nextMarker = resp.NextMarker + } else { + allHostedZoneListed = true + } + } + if hostedZoneFound == nil { + return fmt.Errorf("no matching Route53Zone found") + } + + idHostedZone := cleanZoneID(*hostedZoneFound.Id) + d.SetId(idHostedZone) + d.Set("zone_id", idHostedZone) + d.Set("name", hostedZoneFound.Name) + d.Set("comment", hostedZoneFound.Config.Comment) + d.Set("private_zone", hostedZoneFound.Config.PrivateZone) + d.Set("caller_reference", hostedZoneFound.CallerReference) + d.Set("resource_record_set_count", hostedZoneFound.ResourceRecordSetCount) + return nil +} + +// used to manage trailing . +func hostedZoneName(name string) string { + if strings.HasSuffix(name, ".") { + return name + } else { + return name + "." + } +} diff --git a/builtin/providers/aws/data_source_aws_route53_zone_test.go b/builtin/providers/aws/data_source_aws_route53_zone_test.go new file mode 100644 index 000000000000..4da1b5f3fd39 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_route53_zone_test.go @@ -0,0 +1,133 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceAwsRoute53Zone(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataSourceAwsRoute53ZoneConfig, + Check: resource.ComposeTestCheckFunc( + testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_zone_id"), + testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_name"), + testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_vpc"), + testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_tag"), + ), + }, + }, + }) +} + +func testAccDataSourceAwsRoute53ZoneCheck(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) + } + + hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test"] + if !ok { + return fmt.Errorf("can't find aws_hosted_zone.test in state") + } + attr := rs.Primary.Attributes + if attr["id"] != hostedZone.Primary.Attributes["id"] { + return fmt.Errorf( + "id is %s; want %s", + attr["id"], + hostedZone.Primary.Attributes["id"], + ) + } + + if attr["name"] != "terraformtestacchz.com." { + return fmt.Errorf( + "Route53 Zone name is %s; want terraformtestacchz.com.", + attr["name"], + ) + } + + return nil + } +} + +func testAccDataSourceAwsRoute53ZoneCheckPrivate(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) + } + + hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test_private"] + if !ok { + return fmt.Errorf("can't find aws_hosted_zone.test in state") + } + + attr := rs.Primary.Attributes + if attr["id"] != hostedZone.Primary.Attributes["id"] { + return fmt.Errorf( + "id is %s; want %s", + attr["id"], + hostedZone.Primary.Attributes["id"], + ) + } + + if attr["name"] != "test.acc." { + return fmt.Errorf( + "Route53 Zone name is %s; want test.acc.", + attr["name"], + ) + } + + return nil + } +} + +const testAccDataSourceAwsRoute53ZoneConfig = ` + +provider "aws" { + region = "us-east-2" +} + +resource "aws_vpc" "test" { + cidr_block = "172.16.0.0/16" +} + +resource "aws_route53_zone" "test_private" { + name = "test.acc." + vpc_id = "${aws_vpc.test.id}" + tags { + Environment = "dev" + } +} +data "aws_route53_zone" "by_vpc" { + name = "${aws_route53_zone.test_private.name}" + vpc_id = "${aws_vpc.test.id}" +} + +data "aws_route53_zone" "by_tag" { + name = "${aws_route53_zone.test_private.name}" + private_zone = true + tags { + Environment = "dev" + } +} + +resource "aws_route53_zone" "test" { + name = "terraformtestacchz.com." +} +data "aws_route53_zone" "by_zone_id" { + zone_id = "${aws_route53_zone.test.zone_id}" +} + +data "aws_route53_zone" "by_name" { + name = "${data.aws_route53_zone.by_zone_id.name}" +} + +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 5576544454a2..53ee74b81e1d 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -151,6 +151,7 @@ func Provider() terraform.ResourceProvider { "aws_cloudformation_stack": dataSourceAwsCloudFormationStack(), "aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(), "aws_elb_service_account": dataSourceAwsElbServiceAccount(), + "aws_route53_zone": dataSourceAwsRoute53Zone(), "aws_iam_policy_document": dataSourceAwsIamPolicyDocument(), "aws_ip_ranges": dataSourceAwsIPRanges(), "aws_prefix_list": dataSourceAwsPrefixList(), diff --git a/website/source/docs/providers/aws/d/hosted_zone.html.markdown b/website/source/docs/providers/aws/d/hosted_zone.html.markdown new file mode 100644 index 000000000000..c7a6e05fac00 --- /dev/null +++ b/website/source/docs/providers/aws/d/hosted_zone.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "aws" +page_title: "AWS: aws_hosted_zone" +sidebar_current: "docs-aws-datasource-hosted-zone" +description: |- + Provides details about a specific Hosted Zone +--- + +# aws\_hosted\_zone + +`aws_hosted_zone` provides details about a specific Hosted Zone. + +This data source allows to find a Hosted Zone ID given Hosted Zone name and certain search criteria. + +## Example Usage + +The following example shows how to get a Hosted Zone from it's name and from this data how to create a Record Set. + + +``` +data "aws_route53_zone" "selected" { + name = "test.com." + private_zone = true +} + +resource "aws_route53_record" "www" { + zone_id = "${data.aws_route53_zone.selected.zone_id}" + name = "www.${data.aws_route53_zone.selected.name}" + type = "A" + ttl = "300" + records = ["10.0.0.1"] +} +``` + +## Argument Reference + +The arguments of this data source act as filters for querying the available +Hosted Zone. You have to use `zone_id` or `name`, not both of them. The given filter must match exactly one +Hosted Zone. If you use `name` field for private Hosted Zone, you need to add `private_zone` field to `true` + +* `zone_id` - (Optional) The Hosted Zone id of the desired Hosted Zone. + +* `name` - (Optional) The Hosted Zone name of the desired Hosted Zone. +* `private_zone` - (Optional) Used with `name` field to get a private Hosted Zone. +* `vpc_id` - (Optional) Used with `name` field to get a private Hosted Zone associated with the vpc_id (in this case, private_zone is not mandatory). +* `tags` - (Optional) Used with `name` field. A mapping of tags, each pair of which must exactly match +a pair on the desired security group. +## Attributes Reference + +All of the argument attributes are also exported as +result attributes. This data source will complete the data by populating +any fields that are not included in the configuration with the data for +the selected Hosted Zone. + +The following attribute is additionally exported: + +* `caller_reference` - Caller Reference of the Hosted Zone. +* `comment` - The comment field of the Hosted Zone. +* `resource_record_set_count` - the number of Record Set in the Hosted Zone diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index cfa17020aea4..3e643d16a3fc 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -35,6 +35,9 @@ > aws_elb_service_account + > + aws_hosted_zone + > aws_iam_policy_document