From 3d665ddfcf10e8ef48022d32d60481422d0d8872 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 1 May 2015 11:02:06 +0100 Subject: [PATCH] provider/aws: Add support for alias record to Route53 --- .../aws/resource_aws_route53_record.go | 83 +++++-- .../aws/resource_aws_route53_record_test.go | 219 ++++++++++++++++++ .../aws/r/route53_record.html.markdown | 42 ++++ 3 files changed, 331 insertions(+), 13 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index 1b0824dd6..19fb87fdd 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -1,6 +1,7 @@ package aws import ( + "bytes" "fmt" "log" "strings" @@ -41,8 +42,9 @@ func resourceAwsRoute53Record() *schema.Resource { }, "ttl": &schema.Schema{ - Type: schema.TypeInt, - Required: true, + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"alias"}, }, "weight": &schema.Schema{ @@ -56,10 +58,36 @@ func resourceAwsRoute53Record() *schema.Resource { 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{ - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Required: true, + Type: schema.TypeSet, + ConflictsWith: []string{"alias"}, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, Set: func(v interface{}) int { 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) { - recs := d.Get("records").(*schema.Set).List() - - records := expandResourceRecords(recs, d.Get("type").(string)) - // get expanded name 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 // here. rec := &route53.ResourceRecordSet{ - Name: aws.String(en), - Type: aws.String(d.Get("type").(string)), - TTL: aws.Long(int64(d.Get("ttl").(int))), - ResourceRecords: records, + Name: aws.String(en), + Type: aws.String(d.Get("type").(string)), + } + + 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 { @@ -370,3 +417,13 @@ func expandRecordName(name, zone string) string { } 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()) +} diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index 85c51dd3b..7650fd504 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -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 { conn := testAccProvider.Meta().(*AWSClient).r53conn for _, rs := range s.RootModule().Resources { @@ -325,3 +376,171 @@ resource "aws_route53_record" "www-live" { 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 + } +} +` diff --git a/website/source/docs/providers/aws/r/route53_record.html.markdown b/website/source/docs/providers/aws/r/route53_record.html.markdown index 441dc313c..11e059eba 100644 --- a/website/source/docs/providers/aws/r/route53_record.html.markdown +++ b/website/source/docs/providers/aws/r/route53_record.html.markdown @@ -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 The following arguments are supported: @@ -61,6 +94,15 @@ The following arguments are supported: * `weight` - (Optional) The weight of weighted record (0-255). * `set_identifier` - (Optional) Unique identifier to differentiate weighted 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