Merge pull request #1775 from TimeIncOSS/r53-alias-record

aws: Add support for Alias records into AWS Route 53
This commit is contained in:
Mitchell Hashimoto 2015-05-04 14:03:29 -07:00
commit 8633c88723
5 changed files with 338 additions and 13 deletions

View File

@ -177,6 +177,11 @@ func resourceAwsElb() *schema.Resource {
Computed: true, Computed: true,
}, },
"zone_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(), "tags": tagsSchema(),
}, },
} }
@ -281,6 +286,7 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error {
d.Set("name", *lb.LoadBalancerName) d.Set("name", *lb.LoadBalancerName)
d.Set("dns_name", *lb.DNSName) d.Set("dns_name", *lb.DNSName)
d.Set("zone_id", *lb.CanonicalHostedZoneNameID)
d.Set("internal", *lb.Scheme == "internal") d.Set("internal", *lb.Scheme == "internal")
d.Set("availability_zones", lb.AvailabilityZones) d.Set("availability_zones", lb.AvailabilityZones)
d.Set("instances", flattenInstances(lb.Instances)) d.Set("instances", flattenInstances(lb.Instances))

View File

@ -1,6 +1,7 @@
package aws package aws
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"strings" "strings"
@ -41,8 +42,9 @@ func resourceAwsRoute53Record() *schema.Resource {
}, },
"ttl": &schema.Schema{ "ttl": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Optional: true,
ConflictsWith: []string{"alias"},
}, },
"weight": &schema.Schema{ "weight": &schema.Schema{
@ -56,10 +58,36 @@ func resourceAwsRoute53Record() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"alias": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ConflictsWith: []string{"records", "ttl"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"zone_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"evaluate_target_health": &schema.Schema{
Type: schema.TypeBool,
Required: true,
},
},
},
Set: resourceAwsRoute53AliasRecordHash,
},
"records": &schema.Schema{ "records": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString}, ConflictsWith: []string{"alias"},
Required: true, Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Set: func(v interface{}) int { Set: func(v interface{}) int {
return hashcode.String(v.(string)) return hashcode.String(v.(string))
}, },
@ -309,10 +337,6 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er
} }
func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (*route53.ResourceRecordSet, error) { func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (*route53.ResourceRecordSet, error) {
recs := d.Get("records").(*schema.Set).List()
records := expandResourceRecords(recs, d.Get("type").(string))
// get expanded name // get expanded name
en := expandRecordName(d.Get("name").(string), zoneName) en := expandRecordName(d.Get("name").(string), zoneName)
@ -321,10 +345,33 @@ func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (
// not require the trailing ".", which it will itself, so we don't call FQDN // not require the trailing ".", which it will itself, so we don't call FQDN
// here. // here.
rec := &route53.ResourceRecordSet{ rec := &route53.ResourceRecordSet{
Name: aws.String(en), Name: aws.String(en),
Type: aws.String(d.Get("type").(string)), Type: aws.String(d.Get("type").(string)),
TTL: aws.Long(int64(d.Get("ttl").(int))), }
ResourceRecords: records,
if v, ok := d.GetOk("ttl"); ok {
rec.TTL = aws.Long(int64(v.(int)))
}
// Resource records
if v, ok := d.GetOk("records"); ok {
recs := v.(*schema.Set).List()
rec.ResourceRecords = expandResourceRecords(recs, d.Get("type").(string))
}
// Alias record
if v, ok := d.GetOk("alias"); ok {
aliases := v.(*schema.Set).List()
if len(aliases) > 1 {
return nil, fmt.Errorf("You can only define a single alias target per record")
}
alias := aliases[0].(map[string]interface{})
rec.AliasTarget = &route53.AliasTarget{
DNSName: aws.String(alias["name"].(string)),
EvaluateTargetHealth: aws.Boolean(alias["evaluate_target_health"].(bool)),
HostedZoneID: aws.String(alias["zone_id"].(string)),
}
log.Printf("[DEBUG] Creating alias: %#v", alias)
} }
if v, ok := d.GetOk("weight"); ok { if v, ok := d.GetOk("weight"); ok {
@ -370,3 +417,13 @@ func expandRecordName(name, zone string) string {
} }
return rn return rn
} }
func resourceAwsRoute53AliasRecordHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["zone_id"].(string)))
buf.WriteString(fmt.Sprintf("%t-", m["evaluate_target_health"].(bool)))
return hashcode.String(buf.String())
}

View File

@ -139,6 +139,57 @@ func TestAccRoute53Record_weighted(t *testing.T) {
}) })
} }
func TestAccRoute53Record_alias(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRoute53RecordDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRoute53ElbAliasRecord,
Check: resource.ComposeTestCheckFunc(
testAccCheckRoute53RecordExists("aws_route53_record.elb_alias"),
),
},
resource.TestStep{
Config: testAccRoute53AliasRecord,
Check: resource.ComposeTestCheckFunc(
testAccCheckRoute53RecordExists("aws_route53_record.origin"),
testAccCheckRoute53RecordExists("aws_route53_record.alias"),
),
},
},
})
}
func TestAccRoute53Record_weighted_alias(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRoute53RecordDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccRoute53WeightedElbAliasRecord,
Check: resource.ComposeTestCheckFunc(
testAccCheckRoute53RecordExists("aws_route53_record.elb_weighted_alias_live"),
testAccCheckRoute53RecordExists("aws_route53_record.elb_weighted_alias_dev"),
),
},
resource.TestStep{
Config: testAccRoute53WeightedR53AliasRecord,
Check: resource.ComposeTestCheckFunc(
testAccCheckRoute53RecordExists("aws_route53_record.green_origin"),
testAccCheckRoute53RecordExists("aws_route53_record.r53_weighted_alias_live"),
testAccCheckRoute53RecordExists("aws_route53_record.blue_origin"),
testAccCheckRoute53RecordExists("aws_route53_record.r53_weighted_alias_dev"),
),
},
},
})
}
func testAccCheckRoute53RecordDestroy(s *terraform.State) error { func testAccCheckRoute53RecordDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).r53conn conn := testAccProvider.Meta().(*AWSClient).r53conn
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
@ -325,3 +376,171 @@ resource "aws_route53_record" "www-live" {
records = ["dev.notexample.com"] records = ["dev.notexample.com"]
} }
` `
const testAccRoute53ElbAliasRecord = `
resource "aws_route53_zone" "main" {
name = "notexample.com"
}
resource "aws_route53_record" "alias" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "A"
alias {
zone_id = "${aws_elb.main.zone_id}"
name = "${aws_elb.main.dns_name}"
evaluate_target_health = true
}
}
resource "aws_elb" "main" {
name = "foobar-terraform-elb"
availability_zones = ["us-west-2a"]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
}
`
const testAccRoute53AliasRecord = `
resource "aws_route53_zone" "main" {
name = "notexample.com"
}
resource "aws_route53_record" "origin" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "origin"
type = "A"
ttl = 5
records = ["127.0.0.1"]
}
resource "aws_route53_record" "alias" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "A"
alias {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "${aws_route53_record.origin.name}.${aws_route53_zone.main.name}"
evaluate_target_health = true
}
}
`
const testAccRoute53WeightedElbAliasRecord = `
resource "aws_route53_zone" "main" {
name = "notexample.com"
}
resource "aws_elb" "live" {
name = "foobar-terraform-elb-live"
availability_zones = ["us-west-2a"]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
}
resource "aws_route53_record" "elb_weighted_alias_live" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "A"
weight = 90
set_identifier = "live"
alias {
zone_id = "${aws_elb.live.zone_id}"
name = "${aws_elb.live.dns_name}"
evaluate_target_health = true
}
}
resource "aws_elb" "dev" {
name = "foobar-terraform-elb-dev"
availability_zones = ["us-west-2a"]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
}
resource "aws_route53_record" "elb_weighted_alias_dev" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "A"
weight = 10
set_identifier = "dev"
alias {
zone_id = "${aws_elb.dev.zone_id}"
name = "${aws_elb.dev.dns_name}"
evaluate_target_health = true
}
}
`
const testAccRoute53WeightedR53AliasRecord = `
resource "aws_route53_zone" "main" {
name = "notexample.com"
}
resource "aws_route53_record" "blue_origin" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "blue-origin"
type = "CNAME"
ttl = 5
records = ["v1.terraform.io"]
}
resource "aws_route53_record" "r53_weighted_alias_live" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "CNAME"
weight = 90
set_identifier = "blue"
alias {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "${aws_route53_record.blue_origin.name}.${aws_route53_zone.main.name}"
evaluate_target_health = false
}
}
resource "aws_route53_record" "green_origin" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "green-origin"
type = "CNAME"
ttl = 5
records = ["v2.terraform.io"]
}
resource "aws_route53_record" "r53_weighted_alias_dev" {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "www"
type = "CNAME"
weight = 10
set_identifier = "green"
alias {
zone_id = "${aws_route53_zone.main.zone_id}"
name = "${aws_route53_record.green_origin.name}.${aws_route53_zone.main.name}"
evaluate_target_health = false
}
}
`

View File

@ -96,3 +96,4 @@ The following attributes are exported:
* `source_security_group` - The name of the security group that you can use as * `source_security_group` - The name of the security group that you can use as
part of your inbound rules for your load balancer's back-end application part of your inbound rules for your load balancer's back-end application
instances. instances.
* `zone_id` - The canonical hosted zone ID of the ELB (to be used in a Route 53 Alias record)

View File

@ -49,6 +49,39 @@ resource "aws_route53_record" "www-live" {
} }
``` ```
### Alias record
See [related part of AWS Route53 Developer Guide](http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-choosing-alias-non-alias.html)
to understand differences between alias and non-alias records.
TTL for all alias records is [60 seconds](http://aws.amazon.com/route53/faqs/#dns_failover_do_i_need_to_adjust),
you cannot change this, therefore `ttl` has to be ommitted in alias records.
```
resource "aws_elb" "main" {
name = "foobar-terraform-elb"
availability_zones = ["us-east-1c"]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
}
resource "aws_route53_record" "www" {
zone_id = "${aws_route53_zone.primary.zone_id}"
name = "example.com"
type = "A"
alias {
name = "${aws_elb.main.dns_name}"
zone_id = "${aws_elb.main.zone_id}"
evaluate_target_health = true
}
}
```
## Argument Reference ## Argument Reference
The following arguments are supported: The following arguments are supported:
@ -61,6 +94,15 @@ The following arguments are supported:
* `weight` - (Optional) The weight of weighted record (0-255). * `weight` - (Optional) The weight of weighted record (0-255).
* `set_identifier` - (Optional) Unique identifier to differentiate weighted * `set_identifier` - (Optional) Unique identifier to differentiate weighted
record from one another. Required for each weighted record. record from one another. Required for each weighted record.
* `alias` - (Optional) An alias block. Alias record documented below.
Exactly one of `records` or `alias` must be specified: this determines whether it's an alias record.
Alias records support the following:
* `name` - (Required) DNS domain name for a CloudFront distribution, S3 bucket, ELB, or another resource record set in this hosted zone.
* `zone_id` - (Required) Hosted zone ID for a CloudFront distribution, S3 bucket, ELB, or Route 53 hosted zone. See [`resource_elb.zone_id`](/docs/providers/aws/r/elb.html#zone_id) for example.
* `evaluate_target_health` - (Required) Set to `true` if you want Route 53 to determine whether to respond to DNS queries using this resource record set by checking the health of the resource record set. Some resources have special requirements, see [related part of documentation](http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-values.html#rrsets-values-alias-evaluate-target-health).
## Attributes Reference ## Attributes Reference