diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index fcd781c61..abfa04942 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -173,7 +173,8 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro // Scan for a matching record found := false for _, record := range resp.ResourceRecordSets { - if FQDN(*record.Name) != FQDN(*lopts.StartRecordName) { + name := cleanRecordName(*record.Name) + if FQDN(name) != FQDN(*lopts.StartRecordName) { continue } if strings.ToUpper(*record.Type) != strings.ToUpper(*lopts.StartRecordType) { @@ -232,15 +233,17 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er Refresh: func() (interface{}, string, error) { _, err := conn.ChangeResourceRecordSets(req) if err != nil { - if strings.Contains(err.Error(), "PriorRequestNotComplete") { - // There is some pending operation, so just retry - // in a bit. - return 42, "rejected", nil - } + if r53err, ok := err.(aws.APIError); ok { + if r53err.Code == "PriorRequestNotComplete" { + // There is some pending operation, so just retry + // in a bit. + return 42, "rejected", nil + } - if strings.Contains(err.Error(), "InvalidChangeBatch") { - // This means that the record is already gone. - return 42, "accepted", nil + if r53err.Code == "InvalidChangeBatch" { + // This means that the record is already gone. + return 42, "accepted", nil + } } return 42, "failure", err @@ -282,3 +285,15 @@ func FQDN(name string) string { return name + "." } } + +// Route 53 stores the "*" wildcard indicator as ASCII 42 and returns the +// octal equivalent, "\\052". Here we look for that, and convert back to "*" +// as needed. +func cleanRecordName(name string) string { + str := name + if strings.HasPrefix(name, "\\052") { + str = strings.Replace(name, "\\052", "*", 1) + log.Printf("[DEBUG] Replacing octal \\052 for * in: %s", name) + } + return str +} diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index 08325c783..0608c51d1 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -9,9 +9,26 @@ import ( "github.com/hashicorp/terraform/terraform" "github.com/hashicorp/aws-sdk-go/aws" - awsr53 "github.com/hashicorp/aws-sdk-go/gen/route53" + route53 "github.com/hashicorp/aws-sdk-go/gen/route53" ) +func TestCleanRecordName(t *testing.T) { + cases := []struct { + Input, Output string + }{ + {"www.nonexample.com", "www.nonexample.com"}, + {"\\052.nonexample.com", "*.nonexample.com"}, + {"nonexample.com", "nonexample.com"}, + } + + for _, tc := range cases { + actual := cleanRecordName(tc.Input) + if actual != tc.Output { + t.Fatalf("input: %s\noutput: %s", tc.Input, actual) + } + } +} + func TestAccRoute53Record(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -44,6 +61,30 @@ func TestAccRoute53Record_generatesSuffix(t *testing.T) { }) } +func TestAccRoute53Record_wildcard(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRoute53WildCardRecordConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.wildcard"), + ), + }, + + // Cause a change, which will trigger a refresh + resource.TestStep{ + Config: testAccRoute53WildCardRecordConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists("aws_route53_record.wildcard"), + ), + }, + }, + }) +} + func testAccCheckRoute53RecordDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).r53conn for _, rs := range s.RootModule().Resources { @@ -56,7 +97,7 @@ func testAccCheckRoute53RecordDestroy(s *terraform.State) error { name := parts[1] rType := parts[2] - lopts := &awsr53.ListResourceRecordSetsRequest{ + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), StartRecordName: aws.String(name), StartRecordType: aws.String(rType), @@ -94,7 +135,7 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { name := parts[1] rType := parts[2] - lopts := &awsr53.ListResourceRecordSetsRequest{ + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), StartRecordName: aws.String(name), StartRecordType: aws.String(rType), @@ -107,11 +148,14 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { if len(resp.ResourceRecordSets) == 0 { return fmt.Errorf("Record does not exist") } - rec := resp.ResourceRecordSets[0] - if FQDN(*rec.Name) == FQDN(name) && *rec.Type == rType { - return nil + // rec := resp.ResourceRecordSets[0] + for _, rec := range resp.ResourceRecordSets { + recName := cleanRecordName(*rec.Name) + if FQDN(recName) == FQDN(name) && *rec.Type == rType { + return nil + } } - return fmt.Errorf("Record does not exist: %#v", rec) + return fmt.Errorf("Record does not exist: %#v", rs.Primary.ID) } } @@ -142,3 +186,47 @@ resource "aws_route53_record" "default" { records = ["127.0.0.1", "127.0.0.27"] } ` + +const testAccRoute53WildCardRecordConfig = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "subdomain" + type = "A" + ttl = "30" + records = ["127.0.0.1", "127.0.0.27"] +} + +resource "aws_route53_record" "wildcard" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "*.notexample.com" + type = "A" + ttl = "30" + records = ["127.0.0.1"] +} +` + +const testAccRoute53WildCardRecordConfigUpdate = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "subdomain" + type = "A" + ttl = "30" + records = ["127.0.0.1", "127.0.0.27"] +} + +resource "aws_route53_record" "wildcard" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "*.notexample.com" + type = "A" + ttl = "60" + records = ["127.0.0.1"] +} +`