diff --git a/aws/resource_aws_route53_record.go b/aws/resource_aws_route53_record.go index b9f507d47a9..345e1d777b6 100644 --- a/aws/resource_aws_route53_record.go +++ b/aws/resource_aws_route53_record.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "regexp" + "strconv" "strings" "time" @@ -20,8 +21,8 @@ import ( "github.com/aws/aws-sdk-go/service/route53" ) -var r53NoRecordsFound = errors.New("No matching Hosted Zone found") -var r53NoHostedZoneFound = errors.New("No matching records found") +var r53NoRecordsFound = errors.New("No matching records found") +var r53NoHostedZoneFound = errors.New("No matching Hosted Zone found") var r53ValidRecordTypes = regexp.MustCompile("^(A|AAAA|CAA|CNAME|MX|NAPTR|NS|PTR|SOA|SPF|SRV|TXT)$") func resourceAwsRoute53Record() *schema.Resource { @@ -374,7 +375,8 @@ func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) er return err } - return resourceAwsRoute53RecordRead(d, meta) + _, err = findRecord(d, meta) + return err } func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) error { @@ -451,7 +453,8 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er return err } - return resourceAwsRoute53RecordRead(d, meta) + _, err = findRecord(d, meta) + return err } func changeRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) { @@ -630,27 +633,49 @@ func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceReco log.Printf("[DEBUG] Expanded record name: %s", en) d.Set("fqdn", en) + recordName := FQDN(strings.ToLower(en)) + recordType := d.Get("type").(string) + recordSetIdentifier := d.Get("set_identifier") + + // If this isn't a Weighted, Latency, Geo, or Failover resource with + // a SetIdentifier we only need to look at the first record in the response since there can be + // only one + maxItems := "1" + if recordSetIdentifier != "" { + maxItems = "100" + } + lopts := &route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(cleanZoneID(zone)), - StartRecordName: aws.String(en), - StartRecordType: aws.String(d.Get("type").(string)), + StartRecordName: aws.String(recordName), + StartRecordType: aws.String(recordType), + MaxItems: aws.String(maxItems), } log.Printf("[DEBUG] List resource records sets for zone: %s, opts: %s", zone, lopts) var record *route53.ResourceRecordSet + + // We need to loop over all records starting from the record we are looking for because + // Weighted, Latency, Geo, and Failover resource record sets have a special option + // called SetIdentifier which allows multiple entries with the same name and type but + // a different SetIdentifier + // For all other records we are setting the maxItems to 1 so that we don't return extra + // unneeded records err = conn.ListResourceRecordSetsPages(lopts, func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) bool { for _, recordSet := range resp.ResourceRecordSets { - name := cleanRecordName(*recordSet.Name) - if FQDN(strings.ToLower(name)) != FQDN(strings.ToLower(*lopts.StartRecordName)) { + + responseName := strings.ToLower(cleanRecordName(*recordSet.Name)) + responseType := strings.ToUpper(*recordSet.Type) + + if recordName != responseName { continue } - if strings.ToUpper(*recordSet.Type) != strings.ToUpper(*lopts.StartRecordType) { + if recordType != responseType { continue } - - if recordSet.SetIdentifier != nil && *recordSet.SetIdentifier != d.Get("set_identifier") { + if recordSet.SetIdentifier != nil && *recordSet.SetIdentifier != recordSetIdentifier { continue } @@ -883,16 +908,17 @@ func FQDN(name string) string { } } -// 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. +// Route 53 stores certain characters with the octal equivalent in ASCII format. +// This function converts all of these characters back into the original character +// E.g. "*" is stored as "\\052" and "@" as "\\100" + 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) + s, err := strconv.Unquote(`"` + str + `"`) + if err != nil { + return str } - return str + return s } // Check if the current record name contains the zone suffix. diff --git a/aws/resource_aws_route53_record_test.go b/aws/resource_aws_route53_record_test.go index 72bb533d25a..550d732cd03 100644 --- a/aws/resource_aws_route53_record_test.go +++ b/aws/resource_aws_route53_record_test.go @@ -22,6 +22,8 @@ func TestCleanRecordName(t *testing.T) { }{ {"www.nonexample.com", "www.nonexample.com"}, {"\\052.nonexample.com", "*.nonexample.com"}, + {"\\100.nonexample.com", "@.nonexample.com"}, + {"\\043.nonexample.com", "#.nonexample.com"}, {"nonexample.com", "nonexample.com"}, }