Skip to content

Commit 3239138

Browse files
mathieuherbertstack72
authored andcommitted
provider/aws: data source for AWS Hosted Zone (hashicorp#9766)
* provider/aws: data source for AWS Hosted Zone * add caller_reference, resource_record_set_count fields, manage private zone and trailing dot * fix fmt * update documentation, use string function in hostedZoneNamewq * add vpc_id support * add tags support * add documentation for hosted zone data source tags support
1 parent b2136be commit 3239138

5 files changed

Lines changed: 372 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/route53"
9+
"github.com/hashicorp/terraform/helper/schema"
10+
)
11+
12+
func dataSourceAwsRoute53Zone() *schema.Resource {
13+
return &schema.Resource{
14+
Read: dataSourceAwsRoute53ZoneRead,
15+
16+
Schema: map[string]*schema.Schema{
17+
"zone_id": {
18+
Type: schema.TypeString,
19+
Optional: true,
20+
Computed: true,
21+
},
22+
"name": {
23+
Type: schema.TypeString,
24+
Optional: true,
25+
Computed: true,
26+
},
27+
"private_zone": {
28+
Type: schema.TypeBool,
29+
Optional: true,
30+
Default: false,
31+
},
32+
"comment": {
33+
Type: schema.TypeString,
34+
Optional: true,
35+
Computed: true,
36+
},
37+
"caller_reference": {
38+
Type: schema.TypeString,
39+
Optional: true,
40+
Computed: true,
41+
},
42+
"vpc_id": {
43+
Type: schema.TypeString,
44+
Optional: true,
45+
Computed: true,
46+
},
47+
"tags": tagsSchemaComputed(),
48+
"resource_record_set_count": {
49+
Type: schema.TypeInt,
50+
Optional: true,
51+
Computed: true,
52+
},
53+
},
54+
}
55+
}
56+
57+
func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error {
58+
conn := meta.(*AWSClient).r53conn
59+
name, nameExists := d.GetOk("name")
60+
name = hostedZoneName(name.(string))
61+
id, idExists := d.GetOk("zone_id")
62+
vpcId, vpcIdExists := d.GetOk("vpc_id")
63+
tags := tagsFromMap(d.Get("tags").(map[string]interface{}))
64+
if nameExists && idExists {
65+
return fmt.Errorf("zone_id and name arguments can't be used together")
66+
} else if !nameExists && !idExists {
67+
return fmt.Errorf("Either name or zone_id must be set")
68+
}
69+
70+
var nextMarker *string
71+
72+
var hostedZoneFound *route53.HostedZone
73+
// We loop through all hostedzone
74+
for allHostedZoneListed := false; !allHostedZoneListed; {
75+
req := &route53.ListHostedZonesInput{}
76+
if nextMarker != nil {
77+
req.Marker = nextMarker
78+
}
79+
resp, err := conn.ListHostedZones(req)
80+
81+
if err != nil {
82+
return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", err)
83+
}
84+
for _, hostedZone := range resp.HostedZones {
85+
hostedZoneId := cleanZoneID(*hostedZone.Id)
86+
if idExists && hostedZoneId == id.(string) {
87+
hostedZoneFound = hostedZone
88+
break
89+
// 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
90+
} else if *hostedZone.Name == name && (*hostedZone.Config.PrivateZone == d.Get("private_zone").(bool) || (*hostedZone.Config.PrivateZone == true && vpcIdExists)) {
91+
matchingVPC := false
92+
if vpcIdExists {
93+
reqHostedZone := &route53.GetHostedZoneInput{}
94+
reqHostedZone.Id = aws.String(hostedZoneId)
95+
96+
respHostedZone, errHostedZone := conn.GetHostedZone(reqHostedZone)
97+
if errHostedZone != nil {
98+
return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", errHostedZone)
99+
}
100+
// we go through all VPCs
101+
for _, vpc := range respHostedZone.VPCs {
102+
if *vpc.VPCId == vpcId.(string) {
103+
matchingVPC = true
104+
break
105+
}
106+
}
107+
} else {
108+
matchingVPC = true
109+
}
110+
// we check if tags match
111+
matchingTags := true
112+
if len(tags) > 0 {
113+
reqListTags := &route53.ListTagsForResourceInput{}
114+
reqListTags.ResourceId = aws.String(hostedZoneId)
115+
reqListTags.ResourceType = aws.String("hostedzone")
116+
respListTags, errListTags := conn.ListTagsForResource(reqListTags)
117+
118+
if errListTags != nil {
119+
return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", errListTags)
120+
}
121+
for _, tag := range tags {
122+
found := false
123+
for _, tagRequested := range respListTags.ResourceTagSet.Tags {
124+
if *tag.Key == *tagRequested.Key && *tag.Value == *tagRequested.Value {
125+
found = true
126+
}
127+
}
128+
129+
if !found {
130+
matchingTags = false
131+
break
132+
}
133+
}
134+
135+
}
136+
137+
if matchingTags && matchingVPC {
138+
if hostedZoneFound != nil {
139+
return fmt.Errorf("multplie Route53Zone found please use vpc_id option to filter")
140+
} else {
141+
hostedZoneFound = hostedZone
142+
}
143+
}
144+
}
145+
146+
}
147+
if *resp.IsTruncated {
148+
149+
nextMarker = resp.NextMarker
150+
} else {
151+
allHostedZoneListed = true
152+
}
153+
}
154+
if hostedZoneFound == nil {
155+
return fmt.Errorf("no matching Route53Zone found")
156+
}
157+
158+
idHostedZone := cleanZoneID(*hostedZoneFound.Id)
159+
d.SetId(idHostedZone)
160+
d.Set("zone_id", idHostedZone)
161+
d.Set("name", hostedZoneFound.Name)
162+
d.Set("comment", hostedZoneFound.Config.Comment)
163+
d.Set("private_zone", hostedZoneFound.Config.PrivateZone)
164+
d.Set("caller_reference", hostedZoneFound.CallerReference)
165+
d.Set("resource_record_set_count", hostedZoneFound.ResourceRecordSetCount)
166+
return nil
167+
}
168+
169+
// used to manage trailing .
170+
func hostedZoneName(name string) string {
171+
if strings.HasSuffix(name, ".") {
172+
return name
173+
} else {
174+
return name + "."
175+
}
176+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform/helper/resource"
8+
"github.com/hashicorp/terraform/terraform"
9+
)
10+
11+
func TestAccDataSourceAwsRoute53Zone(t *testing.T) {
12+
resource.Test(t, resource.TestCase{
13+
PreCheck: func() { testAccPreCheck(t) },
14+
Providers: testAccProviders,
15+
Steps: []resource.TestStep{
16+
resource.TestStep{
17+
Config: testAccDataSourceAwsRoute53ZoneConfig,
18+
Check: resource.ComposeTestCheckFunc(
19+
testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_zone_id"),
20+
testAccDataSourceAwsRoute53ZoneCheck("data.aws_route53_zone.by_name"),
21+
testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_vpc"),
22+
testAccDataSourceAwsRoute53ZoneCheckPrivate("data.aws_route53_zone.by_tag"),
23+
),
24+
},
25+
},
26+
})
27+
}
28+
29+
func testAccDataSourceAwsRoute53ZoneCheck(name string) resource.TestCheckFunc {
30+
return func(s *terraform.State) error {
31+
rs, ok := s.RootModule().Resources[name]
32+
if !ok {
33+
return fmt.Errorf("root module has no resource called %s", name)
34+
}
35+
36+
hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test"]
37+
if !ok {
38+
return fmt.Errorf("can't find aws_hosted_zone.test in state")
39+
}
40+
attr := rs.Primary.Attributes
41+
if attr["id"] != hostedZone.Primary.Attributes["id"] {
42+
return fmt.Errorf(
43+
"id is %s; want %s",
44+
attr["id"],
45+
hostedZone.Primary.Attributes["id"],
46+
)
47+
}
48+
49+
if attr["name"] != "terraformtestacchz.com." {
50+
return fmt.Errorf(
51+
"Route53 Zone name is %s; want terraformtestacchz.com.",
52+
attr["name"],
53+
)
54+
}
55+
56+
return nil
57+
}
58+
}
59+
60+
func testAccDataSourceAwsRoute53ZoneCheckPrivate(name string) resource.TestCheckFunc {
61+
return func(s *terraform.State) error {
62+
rs, ok := s.RootModule().Resources[name]
63+
if !ok {
64+
return fmt.Errorf("root module has no resource called %s", name)
65+
}
66+
67+
hostedZone, ok := s.RootModule().Resources["aws_route53_zone.test_private"]
68+
if !ok {
69+
return fmt.Errorf("can't find aws_hosted_zone.test in state")
70+
}
71+
72+
attr := rs.Primary.Attributes
73+
if attr["id"] != hostedZone.Primary.Attributes["id"] {
74+
return fmt.Errorf(
75+
"id is %s; want %s",
76+
attr["id"],
77+
hostedZone.Primary.Attributes["id"],
78+
)
79+
}
80+
81+
if attr["name"] != "test.acc." {
82+
return fmt.Errorf(
83+
"Route53 Zone name is %s; want test.acc.",
84+
attr["name"],
85+
)
86+
}
87+
88+
return nil
89+
}
90+
}
91+
92+
const testAccDataSourceAwsRoute53ZoneConfig = `
93+
94+
provider "aws" {
95+
region = "us-east-2"
96+
}
97+
98+
resource "aws_vpc" "test" {
99+
cidr_block = "172.16.0.0/16"
100+
}
101+
102+
resource "aws_route53_zone" "test_private" {
103+
name = "test.acc."
104+
vpc_id = "${aws_vpc.test.id}"
105+
tags {
106+
Environment = "dev"
107+
}
108+
}
109+
data "aws_route53_zone" "by_vpc" {
110+
name = "${aws_route53_zone.test_private.name}"
111+
vpc_id = "${aws_vpc.test.id}"
112+
}
113+
114+
data "aws_route53_zone" "by_tag" {
115+
name = "${aws_route53_zone.test_private.name}"
116+
private_zone = true
117+
tags {
118+
Environment = "dev"
119+
}
120+
}
121+
122+
resource "aws_route53_zone" "test" {
123+
name = "terraformtestacchz.com."
124+
}
125+
data "aws_route53_zone" "by_zone_id" {
126+
zone_id = "${aws_route53_zone.test.zone_id}"
127+
}
128+
129+
data "aws_route53_zone" "by_name" {
130+
name = "${data.aws_route53_zone.by_zone_id.name}"
131+
}
132+
133+
`

builtin/providers/aws/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func Provider() terraform.ResourceProvider {
157157
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
158158
"aws_eip": dataSourceAwsEip(),
159159
"aws_elb_service_account": dataSourceAwsElbServiceAccount(),
160+
"aws_route53_zone": dataSourceAwsRoute53Zone(),
160161
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
161162
"aws_iam_server_certificate": dataSourceAwsIAMServerCertificate(),
162163
"aws_ip_ranges": dataSourceAwsIPRanges(),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
layout: "aws"
3+
page_title: "AWS: aws_hosted_zone"
4+
sidebar_current: "docs-aws-datasource-hosted-zone"
5+
description: |-
6+
Provides details about a specific Hosted Zone
7+
---
8+
9+
# aws\_hosted\_zone
10+
11+
`aws_hosted_zone` provides details about a specific Hosted Zone.
12+
13+
This data source allows to find a Hosted Zone ID given Hosted Zone name and certain search criteria.
14+
15+
## Example Usage
16+
17+
The following example shows how to get a Hosted Zone from it's name and from this data how to create a Record Set.
18+
19+
20+
```
21+
data "aws_route53_zone" "selected" {
22+
name = "test.com."
23+
private_zone = true
24+
}
25+
26+
resource "aws_route53_record" "www" {
27+
zone_id = "${data.aws_route53_zone.selected.zone_id}"
28+
name = "www.${data.aws_route53_zone.selected.name}"
29+
type = "A"
30+
ttl = "300"
31+
records = ["10.0.0.1"]
32+
}
33+
```
34+
35+
## Argument Reference
36+
37+
The arguments of this data source act as filters for querying the available
38+
Hosted Zone. You have to use `zone_id` or `name`, not both of them. The given filter must match exactly one
39+
Hosted Zone. If you use `name` field for private Hosted Zone, you need to add `private_zone` field to `true`
40+
41+
* `zone_id` - (Optional) The Hosted Zone id of the desired Hosted Zone.
42+
43+
* `name` - (Optional) The Hosted Zone name of the desired Hosted Zone.
44+
* `private_zone` - (Optional) Used with `name` field to get a private Hosted Zone.
45+
* `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).
46+
* `tags` - (Optional) Used with `name` field. A mapping of tags, each pair of which must exactly match
47+
a pair on the desired security group.
48+
## Attributes Reference
49+
50+
All of the argument attributes are also exported as
51+
result attributes. This data source will complete the data by populating
52+
any fields that are not included in the configuration with the data for
53+
the selected Hosted Zone.
54+
55+
The following attribute is additionally exported:
56+
57+
* `caller_reference` - Caller Reference of the Hosted Zone.
58+
* `comment` - The comment field of the Hosted Zone.
59+
* `resource_record_set_count` - the number of Record Set in the Hosted Zone

website/source/layouts/aws.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
<li<%= sidebar_current("docs-aws-datasource-elb-service-account") %>>
5252
<a href="/docs/providers/aws/d/elb_service_account.html">aws_elb_service_account</a>
5353
</li>
54+
<li<%= sidebar_current("docs-aws-datasource-hosted-zone") %>>
55+
<a href="/docs/providers/aws/d/hosted_zone.html">aws_hosted_zone</a>
56+
</li>
5457
<li<%= sidebar_current("docs-aws-datasource-iam-policy-document") %>>
5558
<a href="/docs/providers/aws/d/iam_policy_document.html">aws_iam_policy_document</a>
5659
</li>

0 commit comments

Comments
 (0)