From 7c8b88772f9a7d113831c43c573b8e7422dea3ae Mon Sep 17 00:00:00 2001 From: Bryce Kahle Date: Sun, 21 May 2017 13:23:59 -0700 Subject: [PATCH] Allow import of Route 53 records with underscores in the name Fixes #12637, fixes #14465 Supersedes #14466 --- .../aws/import_aws_route53_record_test.go | 21 +++++++++ .../aws/resource_aws_route53_record.go | 29 ++++++++++-- .../aws/resource_aws_route53_record_test.go | 45 ++++++++++++++++++- 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/import_aws_route53_record_test.go b/builtin/providers/aws/import_aws_route53_record_test.go index 69644ddeb49c..9487e9818bdd 100644 --- a/builtin/providers/aws/import_aws_route53_record_test.go +++ b/builtin/providers/aws/import_aws_route53_record_test.go @@ -26,3 +26,24 @@ func TestAccAwsRoute53Record_importBasic(t *testing.T) { }, }) } + +func TestAccAwsRoute53Record_importUnderscored(t *testing.T) { + resourceName := "aws_route53_record.underscore" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53RecordConfigUnderscoreInName, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"weight"}, + }, + }, + }) +} diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index 42c02c91724c..d0973582f9d8 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "regexp" "strings" "time" @@ -20,6 +21,7 @@ import ( var r53NoRecordsFound = errors.New("No matching Hosted Zone found") var r53NoHostedZoneFound = errors.New("No matching records found") +var r53ValidRecordTypes = regexp.MustCompile("^(A|AAAA|CNAME|MX|NAPTR|NS|PTR|SOA|SPF|SRV|TXT)$") func resourceAwsRoute53Record() *schema.Resource { return &schema.Resource{ @@ -458,18 +460,17 @@ func waitForRoute53RecordSetToSync(conn *route53.Route53, requestId string) erro func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error { // If we don't have a zone ID we're doing an import. Parse it from the ID. if _, ok := d.GetOk("zone_id"); !ok { - parts := strings.Split(d.Id(), "_") - + parts := parseRecordId(d.Id()) //we check that we have parsed the id into the correct number of segments //we need at least 3 segments! - if len(parts) == 1 || len(parts) < 3 { + if parts[0] == "" || parts[1] == "" || parts[2] == "" { return fmt.Errorf("Error Importing aws_route_53 record. Please make sure the record ID is in the form ZONEID_RECORDNAME_TYPE (i.e. Z4KAPRWWNC7JR_dev_A") } d.Set("zone_id", parts[0]) d.Set("name", parts[1]) d.Set("type", parts[2]) - if len(parts) > 3 { + if parts[3] != "" { d.Set("set_identifier", parts[3]) } } @@ -875,3 +876,23 @@ func normalizeAwsAliasName(alias interface{}) string { return strings.TrimRight(input, ".") } + +func parseRecordId(id string) [4]string { + var recZone, recType, recName, recSet string + parts := strings.SplitN(id, "_", 2) + if len(parts) == 2 { + recZone = parts[0] + lastUnderscore := strings.LastIndex(parts[1], "_") + if lastUnderscore != -1 { + recName, recType = parts[1][0:lastUnderscore], parts[1][lastUnderscore+1:] + if !r53ValidRecordTypes.MatchString(recType) { + recSet, recType = recType, "" + lastUnderscore = strings.LastIndex(recName, "_") + if lastUnderscore != -1 { + recName, recType = recName[0:lastUnderscore], recName[lastUnderscore+1:] + } + } + } + } + return [4]string{recZone, recName, recType, recSet} +} diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index 567f36a139fc..425b77a8fcc8 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -52,6 +52,33 @@ func TestExpandRecordName(t *testing.T) { } } +func TestParseRecordId(t *testing.T) { + cases := []struct { + Input, Zone, Name, Type, Set string + }{ + {"ABCDEF_test.notexample.com_A", "ABCDEF", "test.notexample.com", "A", ""}, + {"ABCDEF_test.notexample.com_A_set1", "ABCDEF", "test.notexample.com", "A", "set1"}, + {"ABCDEF__underscore.notexample.com_A", "ABCDEF", "_underscore.notexample.com", "A", ""}, + {"ABCDEF__underscore.notexample.com_A_set1", "ABCDEF", "_underscore.notexample.com", "A", "set1"}, + } + + for _, tc := range cases { + parts := parseRecordId(tc.Input) + if parts[0] != tc.Zone { + t.Fatalf("input: %s\noutput: %s\nexpected:%s", tc.Input, parts[0], tc.Zone) + } + if parts[1] != tc.Name { + t.Fatalf("input: %s\noutput: %s\nexpected:%s", tc.Input, parts[1], tc.Name) + } + if parts[2] != tc.Type { + t.Fatalf("input: %s\noutput: %s\nexpected:%s", tc.Input, parts[2], tc.Type) + } + if parts[3] != tc.Set { + t.Fatalf("input: %s\noutput: %s\nexpected:%s", tc.Input, parts[3], tc.Set) + } + } +} + func TestAccAWSRoute53Record_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -431,7 +458,7 @@ func testAccCheckRoute53RecordDestroy(s *terraform.State) error { continue } - parts := strings.Split(rs.Primary.ID, "_") + parts := parseRecordId(rs.Primary.ID) zone := parts[0] name := parts[1] rType := parts[2] @@ -477,7 +504,7 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { return fmt.Errorf("No hosted zone ID is set") } - parts := strings.Split(rs.Primary.ID, "_") + parts := parseRecordId(rs.Primary.ID) zone := parts[0] name := parts[1] rType := parts[2] @@ -1195,3 +1222,17 @@ resource "aws_route53_record" "long_txt" { ] } ` + +const testAccRoute53RecordConfigUnderscoreInName = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "underscore" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "_underscore.notexample.com" + type = "A" + ttl = "30" + records = ["127.0.0.1"] +} +`