diff --git a/aws/data_source_aws_route53_resolver_rule.go b/aws/data_source_aws_route53_resolver_rule.go index 943a1d9a07f..e491830ae55 100644 --- a/aws/data_source_aws_route53_resolver_rule.go +++ b/aws/data_source_aws_route53_resolver_rule.go @@ -122,7 +122,9 @@ func dataSourceAwsRoute53ResolverRuleRead(d *schema.ResourceData, meta interface d.SetId(aws.StringValue(rule.Id)) arn := *rule.Arn d.Set("arn", arn) - d.Set("domain_name", rule.DomainName) + // To be consistent with other AWS services that do not accept a trailing period, + // we remove the suffix from the Domain Name returned from the API + d.Set("domain_name", trimTrailingPeriod(aws.StringValue(rule.DomainName))) d.Set("name", rule.Name) d.Set("owner_id", rule.OwnerId) d.Set("resolver_endpoint_id", rule.ResolverEndpointId) diff --git a/aws/data_source_aws_route53_zone.go b/aws/data_source_aws_route53_zone.go index 0feff3f9e76..3ad11ae3405 100644 --- a/aws/data_source_aws_route53_zone.go +++ b/aws/data_source_aws_route53_zone.go @@ -3,7 +3,6 @@ package aws import ( "fmt" "log" - "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53" @@ -72,7 +71,7 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig name, nameExists := d.GetOk("name") - name = hostedZoneName(name.(string)) + name = name.(string) id, idExists := d.GetOk("zone_id") vpcId, vpcIdExists := d.GetOk("vpc_id") tags := keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws() @@ -101,12 +100,12 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", err) } for _, hostedZone := range resp.HostedZones { - hostedZoneId := cleanZoneID(*hostedZone.Id) + hostedZoneId := cleanZoneID(aws.StringValue(hostedZone.Id)) if idExists && hostedZoneId == id.(string) { hostedZoneFound = hostedZone break // we check if the name is the same as requested and if private zone field is the same as requested or if there is a vpc_id - } else if *hostedZone.Name == name && (*hostedZone.Config.PrivateZone == d.Get("private_zone").(bool) || (*hostedZone.Config.PrivateZone && vpcIdExists)) { + } else if (trimTrailingPeriod(aws.StringValue(hostedZone.Name)) == trimTrailingPeriod(name)) && (aws.BoolValue(hostedZone.Config.PrivateZone) == d.Get("private_zone").(bool) || (aws.BoolValue(hostedZone.Config.PrivateZone) && vpcIdExists)) { matchingVPC := false if vpcIdExists { reqHostedZone := &route53.GetHostedZoneInput{} @@ -118,7 +117,7 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro } // we go through all VPCs for _, vpc := range respHostedZone.VPCs { - if *vpc.VPCId == vpcId.(string) { + if aws.StringValue(vpc.VPCId) == vpcId.(string) { matchingVPC = true break } @@ -132,7 +131,7 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro listTags, err := keyvaluetags.Route53ListTags(conn, hostedZoneId, route53.TagResourceTypeHostedzone) if err != nil { - return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", err) + return fmt.Errorf("Error finding Route 53 Hosted Zone: %w", err) } matchingTags = listTags.ContainsAll(tags) } @@ -156,10 +155,12 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("no matching Route53Zone found") } - idHostedZone := cleanZoneID(*hostedZoneFound.Id) + idHostedZone := cleanZoneID(aws.StringValue(hostedZoneFound.Id)) d.SetId(idHostedZone) d.Set("zone_id", idHostedZone) - d.Set("name", hostedZoneFound.Name) + // To be consistent with other AWS services (e.g. ACM) that do not accept a trailing period, + // we remove the suffix from the Hosted Zone Name returned from the API + d.Set("name", trimTrailingPeriod(aws.StringValue(hostedZoneFound.Name))) d.Set("comment", hostedZoneFound.Config.Comment) d.Set("private_zone", hostedZoneFound.Config.PrivateZone) d.Set("caller_reference", hostedZoneFound.CallerReference) @@ -173,7 +174,9 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro if err != nil { return fmt.Errorf("Error finding Route 53 Hosted Zone: %v", err) } - d.Set("name_servers", nameServers) + if err := d.Set("name_servers", nameServers); err != nil { + return fmt.Errorf("error setting name_servers: %w", err) + } tags, err = keyvaluetags.Route53ListTags(conn, idHostedZone, route53.TagResourceTypeHostedzone) @@ -188,15 +191,6 @@ func dataSourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) erro return nil } -// used to manage trailing . -func hostedZoneName(name string) string { - if strings.HasSuffix(name, ".") { - return name - } - - return name + "." -} - // used to retrieve name servers func hostedZoneNameServers(id string, conn *route53.Route53) ([]string, error) { req := &route53.GetHostedZoneInput{} @@ -214,7 +208,7 @@ func hostedZoneNameServers(id string, conn *route53.Route53) ([]string, error) { servers := []string{} for _, server := range resp.DelegationSet.NameServers { if server != nil { - servers = append(servers, *server) + servers = append(servers, aws.StringValue(server)) } } return servers, nil diff --git a/aws/data_source_aws_route53_zone_test.go b/aws/data_source_aws_route53_zone_test.go index 2cac4bfa7e7..4a5dad3feea 100644 --- a/aws/data_source_aws_route53_zone_test.go +++ b/aws/data_source_aws_route53_zone_test.go @@ -221,7 +221,7 @@ resource "aws_vpc" "test" { } resource "aws_service_discovery_private_dns_namespace" "test" { - name = "test.acc-sd-%[1]d." + name = "test.acc-sd-%[1]d" vpc = "${aws_vpc.test.id}" } diff --git a/aws/diff_suppress_funcs.go b/aws/diff_suppress_funcs.go index 7597f801308..35c7ee2c87b 100644 --- a/aws/diff_suppress_funcs.go +++ b/aws/diff_suppress_funcs.go @@ -115,14 +115,6 @@ func suppressCloudFormationTemplateBodyDiffs(k, old, new string, d *schema.Resou return normalizedOld == normalizedNew } -func suppressRoute53ZoneNameWithTrailingDot(k, old, new string, d *schema.ResourceData) bool { - // "." is different from "". - if old == "." || new == "." { - return old == new - } - return strings.TrimSuffix(old, ".") == strings.TrimSuffix(new, ".") -} - // suppressEqualCIDRBlockDiffs provides custom difference suppression for CIDR blocks // that have different string values but represent the same CIDR. func suppressEqualCIDRBlockDiffs(k, old, new string, d *schema.ResourceData) bool { diff --git a/aws/diff_suppress_funcs_test.go b/aws/diff_suppress_funcs_test.go index 33e74881e8f..582dec4c8ea 100644 --- a/aws/diff_suppress_funcs_test.go +++ b/aws/diff_suppress_funcs_test.go @@ -264,59 +264,3 @@ Outputs: } } } - -func TestSuppressRoute53ZoneNameWithTrailingDot(t *testing.T) { - testCases := []struct { - old string - new string - equivalent bool - }{ - { - old: "example.com", - new: "example.com", - equivalent: true, - }, - { - old: "example.com.", - new: "example.com.", - equivalent: true, - }, - { - old: "example.com.", - new: "example.com", - equivalent: true, - }, - { - old: "example.com", - new: "example.com.", - equivalent: true, - }, - { - old: ".", - new: "", - equivalent: false, - }, - { - old: "", - new: ".", - equivalent: false, - }, - { - old: ".", - new: ".", - equivalent: true, - }, - } - - for i, tc := range testCases { - value := suppressRoute53ZoneNameWithTrailingDot("test_property", tc.old, tc.new, nil) - - if tc.equivalent && !value { - t.Fatalf("expected test case %d to be equivalent", i) - } - - if !tc.equivalent && value { - t.Fatalf("expected test case %d to not be equivalent", i) - } - } -} diff --git a/aws/resource_aws_acm_certificate.go b/aws/resource_aws_acm_certificate.go index 897eb8c3419..e3bcb42b288 100644 --- a/aws/resource_aws_acm_certificate.go +++ b/aws/resource_aws_acm_certificate.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log" + "regexp" "strings" "time" @@ -57,16 +58,15 @@ func resourceAwsAcmCertificate() *schema.Resource { ForceNew: true, }, "domain_name": { + // AWS Provider 3.0.0 aws_route53_zone references no longer contain a + // trailing period, no longer requiring a custom StateFunc + // to prevent ACM API error Type: schema.TypeString, Optional: true, Computed: true, ForceNew: true, ConflictsWith: []string{"private_key", "certificate_body", "certificate_chain"}, - StateFunc: func(v interface{}) string { - // AWS Provider 1.42.0+ aws_route53_zone references may contain a - // trailing period, which generates an ACM API error - return strings.TrimSuffix(v.(string), ".") - }, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, "subject_alternative_names": { Type: schema.TypeSet, @@ -74,12 +74,11 @@ func resourceAwsAcmCertificate() *schema.Resource { Computed: true, ForceNew: true, Elem: &schema.Schema{ - Type: schema.TypeString, - StateFunc: func(v interface{}) string { - // AWS Provider 1.42.0+ aws_route53_zone references may contain a - // trailing period, which generates an ACM API error - return strings.TrimSuffix(v.(string), ".") - }, + // AWS Provider 3.0.0 aws_route53_zone references no longer contain a + // trailing period, no longer requiring a custom StateFunc + // to prevent ACM API error + Type: schema.TypeString, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, Set: schema.HashString, ConflictsWith: []string{"private_key", "certificate_body", "certificate_chain"}, @@ -164,7 +163,10 @@ func resourceAwsAcmCertificate() *schema.Resource { // Attempt to calculate the domain validation options based on domains present in domain_name and subject_alternative_names if diff.Get("validation_method").(string) == "DNS" && (diff.HasChange("domain_name") || diff.HasChange("subject_alternative_names")) { domainValidationOptionsList := []interface{}{map[string]interface{}{ - "domain_name": strings.TrimSuffix(diff.Get("domain_name").(string), "."), + // AWS Provider 3.0 -- plan-time validation prevents "domain_name" + // argument to accept a string with trailing period; thus, trim of trailing period + // no longer required here + "domain_name": diff.Get("domain_name").(string), }} if sanSet, ok := diff.Get("subject_alternative_names").(*schema.Set); ok { @@ -176,7 +178,10 @@ func resourceAwsAcmCertificate() *schema.Resource { } m := map[string]interface{}{ - "domain_name": strings.TrimSuffix(san, "."), + // AWS Provider 3.0 -- plan-time validation prevents "subject_alternative_names" + // argument to accept strings with trailing period; thus, trim of trailing period + // no longer required here + "domain_name": san, } domainValidationOptionsList = append(domainValidationOptionsList, m) @@ -227,7 +232,7 @@ func resourceAwsAcmCertificateCreateImported(d *schema.ResourceData, meta interf func resourceAwsAcmCertificateCreateRequested(d *schema.ResourceData, meta interface{}) error { acmconn := meta.(*AWSClient).acmconn params := &acm.RequestCertificateInput{ - DomainName: aws.String(strings.TrimSuffix(d.Get("domain_name").(string), ".")), + DomainName: aws.String(d.Get("domain_name").(string)), IdempotencyToken: aws.String(resource.PrefixedUniqueId("tf")), // 32 character limit Options: expandAcmCertificateOptions(d.Get("options").([]interface{})), } @@ -243,7 +248,7 @@ func resourceAwsAcmCertificateCreateRequested(d *schema.ResourceData, meta inter if sans, ok := d.GetOk("subject_alternative_names"); ok { subjectAlternativeNames := make([]*string, len(sans.(*schema.Set).List())) for i, sanRaw := range sans.(*schema.Set).List() { - subjectAlternativeNames[i] = aws.String(strings.TrimSuffix(sanRaw.(string), ".")) + subjectAlternativeNames[i] = aws.String(sanRaw.(string)) } params.SubjectAlternativeNames = subjectAlternativeNames } @@ -389,10 +394,10 @@ func convertValidationOptions(certificate *acm.CertificateDetail) ([]map[string] for _, o := range certificate.DomainValidationOptions { if o.ResourceRecord != nil { validationOption := map[string]interface{}{ - "domain_name": *o.DomainName, - "resource_record_name": *o.ResourceRecord.Name, - "resource_record_type": *o.ResourceRecord.Type, - "resource_record_value": *o.ResourceRecord.Value, + "domain_name": aws.StringValue(o.DomainName), + "resource_record_name": aws.StringValue(o.ResourceRecord.Name), + "resource_record_type": aws.StringValue(o.ResourceRecord.Type), + "resource_record_value": aws.StringValue(o.ResourceRecord.Value), } domainValidationResult = append(domainValidationResult, validationOption) } else if o.ValidationEmails != nil && len(o.ValidationEmails) > 0 { diff --git a/aws/resource_aws_acm_certificate_test.go b/aws/resource_aws_acm_certificate_test.go index 41e179cb421..29635573ddc 100644 --- a/aws/resource_aws_acm_certificate_test.go +++ b/aws/resource_aws_acm_certificate_test.go @@ -249,10 +249,11 @@ func TestAccAWSAcmCertificate_privateCert(t *testing.T) { }) } +// TestAccAWSAcmCertificate_root_TrailingPeriod updated in 3.0 to account for domain_name plan-time validation +// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/13510 func TestAccAWSAcmCertificate_root_TrailingPeriod(t *testing.T) { rootDomain := testAccAwsAcmCertificateDomainFromEnv(t) domain := fmt.Sprintf("%s.", rootDomain) - resourceName := "aws_acm_certificate.cert" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -260,25 +261,8 @@ func TestAccAWSAcmCertificate_root_TrailingPeriod(t *testing.T) { CheckDestroy: testAccCheckAcmCertificateDestroy, Steps: []resource.TestStep{ { - Config: testAccAcmCertificateConfig(domain, acm.ValidationMethodDns), - Check: resource.ComposeTestCheckFunc( - testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile(`certificate/.+`)), - resource.TestCheckResourceAttr(resourceName, "domain_name", strings.TrimSuffix(domain, ".")), - resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), - tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": strings.TrimSuffix(domain, "."), - "resource_record_type": "CNAME", - }), - resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), - resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), - resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), - resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccAcmCertificateConfig(domain, acm.ValidationMethodDns), + ExpectError: regexp.MustCompile(`config is invalid: invalid value for domain_name \(cannot end with a period\)`), }, }, }) diff --git a/aws/resource_aws_route53_record.go b/aws/resource_aws_route53_record.go index 6e172f280ce..68a9c7b7039 100644 --- a/aws/resource_aws_route53_record.go +++ b/aws/resource_aws_route53_record.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/route53" ) @@ -42,6 +41,9 @@ func resourceAwsRoute53Record() *schema.Resource { Required: true, ForceNew: true, StateFunc: func(v interface{}) string { + // AWS Provider aws_acm_certification.domain_validation_options.resource_record_name + // references (and perhaps others) contain a trailing period, requiring a custom StateFunc + // to trim the string to prevent Route53 API error value := strings.TrimSuffix(v.(string), ".") return strings.ToLower(value) }, @@ -273,7 +275,7 @@ func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) er } // Build the to be deleted record - en := expandRecordName(d.Get("name").(string), *zoneRecord.HostedZone.Name) + en := expandRecordName(d.Get("name").(string), aws.StringValue(zoneRecord.HostedZone.Name)) typeo, _ := d.GetChange("type") oldRec := &route53.ResourceRecordSet{ @@ -328,7 +330,7 @@ func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) er } // Build the to be created record - rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name) + rec, err := resourceAwsRoute53RecordBuildSet(d, aws.StringValue(zoneRecord.HostedZone.Name)) if err != nil { return err } @@ -340,27 +342,27 @@ func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) er Comment: aws.String("Managed by Terraform"), Changes: []*route53.Change{ { - Action: aws.String("DELETE"), + Action: aws.String(route53.ChangeActionDelete), ResourceRecordSet: oldRec, }, { - Action: aws.String("CREATE"), + Action: aws.String(route53.ChangeActionCreate), ResourceRecordSet: rec, }, }, } input := &route53.ChangeResourceRecordSetsInput{ - HostedZoneId: aws.String(cleanZoneID(*zoneRecord.HostedZone.Id)), + HostedZoneId: aws.String(cleanZoneID(aws.StringValue(zoneRecord.HostedZone.Id))), ChangeBatch: changeBatch, } log.Printf("[DEBUG] Updating resource records for zone: %s, name: %s\n\n%s", - zone, *rec.Name, input) + zone, aws.StringValue(rec.Name), input) respRaw, err := changeRoute53RecordSet(conn, input) if err != nil { - return fmt.Errorf("[ERR]: Error building changeset: %s", err) + return fmt.Errorf("[ERR]: Error building changeset: %w", err) } changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo @@ -377,7 +379,7 @@ func resourceAwsRoute53RecordUpdate(d *schema.ResourceData, meta interface{}) er d.SetId(strings.Join(vars, "_")) - err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id)) + err = waitForRoute53RecordSetToSync(conn, cleanChangeID(aws.StringValue(changeInfo.Id))) if err != nil { return err } @@ -400,7 +402,7 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er } // Build the record - rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name) + rec, err := resourceAwsRoute53RecordBuildSet(d, aws.StringValue(zoneRecord.HostedZone.Name)) if err != nil { return err } @@ -410,9 +412,9 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er // Else CREATE is used and fail if the same record exists var action string if d.Get("allow_overwrite").(bool) || !d.IsNewResource() { - action = "UPSERT" + action = route53.ChangeActionUpsert } else { - action = "CREATE" + action = route53.ChangeActionCreate } // Create the new records. We abuse StateChangeConf for this to @@ -429,16 +431,16 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er } req := &route53.ChangeResourceRecordSetsInput{ - HostedZoneId: aws.String(cleanZoneID(*zoneRecord.HostedZone.Id)), + HostedZoneId: aws.String(cleanZoneID(aws.StringValue(zoneRecord.HostedZone.Id))), ChangeBatch: changeBatch, } log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s\n\n%s", - zone, *rec.Name, req) + zone, aws.StringValue(rec.Name), req) respRaw, err := changeRoute53RecordSet(conn, req) if err != nil { - return fmt.Errorf("[ERR]: Error building changeset: %s", err) + return fmt.Errorf("[ERR]: Error building changeset: %w", err) } changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo @@ -455,7 +457,7 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er d.SetId(strings.Join(vars, "_")) - err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id)) + err = waitForRoute53RecordSetToSync(conn, cleanChangeID(aws.StringValue(changeInfo.Id))) if err != nil { return err } @@ -469,7 +471,7 @@ func changeRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResource err := resource.Retry(1*time.Minute, func() *resource.RetryError { var err error out, err = conn.ChangeResourceRecordSets(input) - if isAWSErr(err, "NoSuchHostedZone", "") { + if isAWSErr(err, route53.ErrCodeNoSuchHostedZone, "") { log.Print("[DEBUG] Hosted Zone not found, retrying...") return resource.RetryableError(err) } @@ -488,8 +490,8 @@ func changeRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResource func waitForRoute53RecordSetToSync(conn *route53.Route53, requestId string) error { wait := resource.StateChangeConf{ Delay: 30 * time.Second, - Pending: []string{"PENDING"}, - Target: []string{"INSYNC"}, + Pending: []string{route53.ChangeStatusPending}, + Target: []string{route53.ChangeStatusInsync}, Timeout: 30 * time.Minute, MinTimeout: 5 * time.Second, Refresh: func() (result interface{}, state string, err error) { @@ -533,18 +535,18 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro } } - err = d.Set("records", flattenResourceRecords(record.ResourceRecords, *record.Type)) + err = d.Set("records", flattenResourceRecords(record.ResourceRecords, aws.StringValue(record.Type))) if err != nil { - return fmt.Errorf("Error setting records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting records for: %s, error: %w", d.Id(), err) } if alias := record.AliasTarget; alias != nil { - name := normalizeAwsAliasName(*alias.DNSName) + name := normalizeAwsAliasName(aws.StringValue(alias.DNSName)) d.Set("alias", []interface{}{ map[string]interface{}{ - "zone_id": *alias.HostedZoneId, + "zone_id": aws.StringValue(alias.HostedZoneId), "name": name, - "evaluate_target_health": *alias.EvaluateTargetHealth, + "evaluate_target_health": aws.BoolValue(alias.EvaluateTargetHealth), }, }) } @@ -556,7 +558,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro "type": aws.StringValue(record.Failover), }} if err := d.Set("failover_routing_policy", v); err != nil { - return fmt.Errorf("Error setting failover records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting failover records for: %s, error: %w", d.Id(), err) } } @@ -567,7 +569,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro "subdivision": aws.StringValue(record.GeoLocation.SubdivisionCode), }} if err := d.Set("geolocation_routing_policy", v); err != nil { - return fmt.Errorf("Error setting gelocation records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting gelocation records for: %s, error: %w", d.Id(), err) } } @@ -576,7 +578,7 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro "region": aws.StringValue(record.Region), }} if err := d.Set("latency_routing_policy", v); err != nil { - return fmt.Errorf("Error setting latency records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting latency records for: %s, error: %w", d.Id(), err) } } @@ -585,13 +587,13 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro "weight": aws.Int64Value((record.Weight)), }} if err := d.Set("weighted_routing_policy", v); err != nil { - return fmt.Errorf("Error setting weighted records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting weighted records for: %s, error: %w", d.Id(), err) } } if record.MultiValueAnswer != nil { if err := d.Set("multivalue_answer_routing_policy", record.MultiValueAnswer); err != nil { - return fmt.Errorf("Error setting multivalue answer records for: %s, error: %#v", d.Id(), err) + return fmt.Errorf("Error setting multivalue answer records for: %s, error: %w", d.Id(), err) } } @@ -624,9 +626,10 @@ func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceReco // get expanded name zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(zone)}) if err != nil { - if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" { + if isAWSErr(err, route53.ErrCodeNoSuchHostedZone, "") { return nil, r53NoHostedZoneFound } + return nil, err } @@ -640,13 +643,13 @@ func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceReco name = d.Get("name").(string) } - en := expandRecordName(name, *zoneRecord.HostedZone.Name) + en := expandRecordName(name, aws.StringValue(zoneRecord.HostedZone.Name)) 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") + recordSetIdentifier := d.Get("set_identifier").(string) // 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 @@ -678,7 +681,7 @@ func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceReco for _, recordSet := range resp.ResourceRecordSets { responseName := strings.ToLower(cleanRecordName(*recordSet.Name)) - responseType := strings.ToUpper(*recordSet.Type) + responseType := strings.ToUpper(aws.StringValue(recordSet.Type)) if recordName != responseName { continue @@ -686,7 +689,7 @@ func findRecord(d *schema.ResourceData, meta interface{}) (*route53.ResourceReco if recordType != responseType { continue } - if recordSet.SetIdentifier != nil && *recordSet.SetIdentifier != recordSetIdentifier { + if aws.StringValue(recordSet.SetIdentifier) != recordSetIdentifier { continue } @@ -735,7 +738,7 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er Comment: aws.String("Deleted by Terraform"), Changes: []*route53.Change{ { - Action: aws.String("DELETE"), + Action: aws.String(route53.ChangeActionDelete), ResourceRecordSet: rec, }, }, @@ -750,7 +753,7 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er respRaw, err := deleteRoute53RecordSet(conn, req) if err != nil { - return fmt.Errorf("[ERR]: Error building changeset: %s", err) + return fmt.Errorf("[ERR]: Error building changeset: %w", err) } changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo @@ -759,13 +762,13 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er return nil } - err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id)) + err = waitForRoute53RecordSetToSync(conn, cleanChangeID(aws.StringValue(changeInfo.Id))) return err } func deleteRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) { out, err := conn.ChangeResourceRecordSets(input) - if isAWSErr(err, "InvalidChangeBatch", "") { + if isAWSErr(err, route53.ErrCodeInvalidChangeBatch, "") { return out, nil } diff --git a/aws/resource_aws_route53_record_test.go b/aws/resource_aws_route53_record_test.go index e8b62ad56a0..d04ada305be 100644 --- a/aws/resource_aws_route53_record_test.go +++ b/aws/resource_aws_route53_record_test.go @@ -245,6 +245,34 @@ func TestAccAWSRoute53Record_basic_fqdn(t *testing.T) { }) } +// TestAccAWSRoute53Record_basic_trailingPeriodAndZoneID ensures an aws_route53_record +// created with a name configured with a trailing period and explicit zone_id gets imported correctly +func TestAccAWSRoute53Record_basic_trailingPeriodAndZoneID(t *testing.T) { + var record1 route53.ResourceRecordSet + resourceName := "aws_route53_record.default" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRoute53RecordConfig_nameWithTrailingPeriod, + Check: resource.ComposeTestCheckFunc( + testAccCheckRoute53RecordExists(resourceName, &record1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"allow_overwrite", "weight"}, + }, + }, + }) +} + func TestAccAWSRoute53Record_txtSupport(t *testing.T) { var record1 route53.ResourceRecordSet resourceName := "aws_route53_record.default" @@ -1177,6 +1205,20 @@ resource "aws_route53_record" "default" { } ` +const testAccRoute53RecordConfig_nameWithTrailingPeriod = ` +resource "aws_route53_zone" "main" { + name = "notexample.com" +} + +resource "aws_route53_record" "default" { + zone_id = "${aws_route53_zone.main.zone_id}" + name = "www.NOTexamplE.com." + type = "A" + ttl = "30" + records = ["127.0.0.1", "127.0.0.27"] +} +` + const testAccRoute53RecordConfigMultiple = ` resource "aws_route53_zone" "test" { name = "notexample.com" @@ -1934,7 +1976,7 @@ resource "aws_route53_zone" "main" { } resource "aws_route53_record" "sample" { - zone_id = "${aws_route53_zone.main.zone_id}" + zone_id = "${aws_route53_zone.main.zone_id}" name = "sample" type = "CNAME" ttl = "30" diff --git a/aws/resource_aws_route53_resolver_rule.go b/aws/resource_aws_route53_resolver_rule.go index 4730e044a7a..4fbcf598e1d 100644 --- a/aws/resource_aws_route53_resolver_rule.go +++ b/aws/resource_aws_route53_resolver_rule.go @@ -38,11 +38,11 @@ func resourceAwsRoute53ResolverRule() *schema.Resource { Schema: map[string]*schema.Schema{ "domain_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - DiffSuppressFunc: suppressRoute53ZoneNameWithTrailingDot, - ValidateFunc: validation.StringLenBetween(1, 256), + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 256), + StateFunc: trimTrailingPeriod, }, "rule_type": { @@ -163,7 +163,9 @@ func resourceAwsRoute53ResolverRuleRead(d *schema.ResourceData, meta interface{} rule := ruleRaw.(*route53resolver.ResolverRule) d.Set("arn", rule.Arn) - d.Set("domain_name", rule.DomainName) + // To be consistent with other AWS services that do not accept a trailing period, + // we remove the suffix from the Domain Name returned from the API + d.Set("domain_name", trimTrailingPeriod(aws.StringValue(rule.DomainName))) d.Set("name", rule.Name) d.Set("owner_id", rule.OwnerId) d.Set("resolver_endpoint_id", rule.ResolverEndpointId) diff --git a/aws/resource_aws_route53_resolver_rule_test.go b/aws/resource_aws_route53_resolver_rule_test.go index 4318e48b701..8bb487bc321 100644 --- a/aws/resource_aws_route53_resolver_rule_test.go +++ b/aws/resource_aws_route53_resolver_rule_test.go @@ -95,7 +95,7 @@ func TestAccAwsRoute53ResolverRule_basic(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_basicNoTags, Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), testAccCheckResourceAttrAccountID(resourceName, "owner_id"), @@ -124,7 +124,7 @@ func TestAccAwsRoute53ResolverRule_tags(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_basicTags, Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -140,7 +140,7 @@ func TestAccAwsRoute53ResolverRule_tags(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_basicTagsChanged, Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -151,7 +151,7 @@ func TestAccAwsRoute53ResolverRule_tags(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_basicNoTags, Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), resource.TestCheckResourceAttr(resourceName, "share_status", "NOT_SHARED"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), @@ -176,7 +176,7 @@ func TestAccAwsRoute53ResolverRule_updateName(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_basicName(name1), Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name1), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), ), @@ -191,7 +191,7 @@ func TestAccAwsRoute53ResolverRule_updateName(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule2), testAccCheckRoute53ResolverRulesSame(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name2), resource.TestCheckResourceAttr(resourceName, "rule_type", "SYSTEM"), ), @@ -216,7 +216,7 @@ func TestAccAwsRoute53ResolverRule_forward(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_forward(name), Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp1, "id"), @@ -237,7 +237,7 @@ func TestAccAwsRoute53ResolverRule_forward(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule2), testAccCheckRoute53ResolverRulesSame(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp1, "id"), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), @@ -257,7 +257,7 @@ func TestAccAwsRoute53ResolverRule_forward(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule3), testAccCheckRoute53ResolverRulesSame(&rule3, &rule2), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp2, "id"), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), @@ -291,7 +291,7 @@ func TestAccAwsRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { Config: testAccRoute53ResolverRuleConfig_forward(name), Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp, "id"), @@ -307,7 +307,7 @@ func TestAccAwsRoute53ResolverRule_forwardEndpointRecreate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ResolverRuleExists(resourceName, &rule2), testAccCheckRoute53ResolverRulesDifferent(&rule2, &rule1), - resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com."), + resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "rule_type", "FORWARD"), resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", resourceNameEp, "id"), diff --git a/aws/resource_aws_route53_zone.go b/aws/resource_aws_route53_zone.go index f156814317f..1b3ebf53ddf 100644 --- a/aws/resource_aws_route53_zone.go +++ b/aws/resource_aws_route53_zone.go @@ -29,10 +29,14 @@ func resourceAwsRoute53Zone() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - DiffSuppressFunc: suppressRoute53ZoneNameWithTrailingDot, + // AWS Provider 3.0.0 - trailing period removed from name + // returned from API, no longer requiring custom DiffSuppressFunc; + // instead a StateFunc allows input to be provided + // with or without the trailing period + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: trimTrailingPeriod, }, "comment": { @@ -179,7 +183,9 @@ func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error d.Set("comment", "") d.Set("delegation_set_id", "") - d.Set("name", output.HostedZone.Name) + // To be consistent with other AWS services (e.g. ACM) that do not accept a trailing period, + // we remove the suffix from the Hosted Zone Name returned from the API + d.Set("name", trimTrailingPeriod(aws.StringValue(output.HostedZone.Name))) d.Set("zone_id", cleanZoneID(aws.StringValue(output.HostedZone.Id))) var nameServers []string @@ -389,6 +395,22 @@ func cleanZoneID(ID string) string { return strings.TrimPrefix(ID, "/hostedzone/") } +// trimTrailingPeriod is used to remove the trailing period +// of "name" or "domain name" attributes often returned from +// the Route53 API or provided as user input +func trimTrailingPeriod(v interface{}) string { + var str string + switch value := v.(type) { + case *string: + str = *value + case string: + str = value + default: + return "" + } + return strings.TrimSuffix(str, ".") +} + func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) { resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(zoneId), diff --git a/aws/resource_aws_route53_zone_test.go b/aws/resource_aws_route53_zone_test.go index 1263dfbef7c..63f2528bd09 100644 --- a/aws/resource_aws_route53_zone_test.go +++ b/aws/resource_aws_route53_zone_test.go @@ -48,6 +48,23 @@ func TestCleanChangeID(t *testing.T) { } } +func TestTrimTrailingPeriod(t *testing.T) { + cases := []struct { + Input, Output string + }{ + {"example.com", "example.com"}, + {"example.com.", "example.com"}, + {"www.example.com.", "www.example.com"}, + } + + for _, tc := range cases { + actual := trimTrailingPeriod(tc.Input) + if actual != tc.Output { + t.Fatalf("input: %s\noutput: %s", tc.Input, actual) + } + } +} + func TestAccAWSRoute53Zone_basic(t *testing.T) { var zone route53.GetHostedZoneOutput @@ -64,7 +81,7 @@ func TestAccAWSRoute53Zone_basic(t *testing.T) { Config: testAccRoute53ZoneConfig(zoneName), Check: resource.ComposeTestCheckFunc( testAccCheckRoute53ZoneExists(resourceName, &zone), - resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("%s.", zoneName)), + resource.TestCheckResourceAttr(resourceName, "name", zoneName), resource.TestCheckResourceAttr(resourceName, "name_servers.#", "4"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "vpc.#", "0"), @@ -554,7 +571,7 @@ func testAccCheckDomainName(zone *route53.GetHostedZoneOutput, domain string) re return fmt.Errorf("Empty name in HostedZone for domain %s", domain) } - if *zone.HostedZone.Name == domain { + if aws.StringValue(zone.HostedZone.Name) == domain { return nil } diff --git a/aws/resource_aws_ses_domain_identity.go b/aws/resource_aws_ses_domain_identity.go index 4dd77bd63e1..3ec2813e7ee 100644 --- a/aws/resource_aws_ses_domain_identity.go +++ b/aws/resource_aws_ses_domain_identity.go @@ -3,12 +3,13 @@ package aws import ( "fmt" "log" - "strings" + "regexp" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ses" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) func resourceAwsSesDomainIdentity() *schema.Resource { @@ -26,12 +27,10 @@ func resourceAwsSesDomainIdentity() *schema.Resource { Computed: true, }, "domain": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: func(v interface{}) string { - return strings.TrimSuffix(v.(string), ".") - }, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, "verification_token": { Type: schema.TypeString, @@ -45,7 +44,6 @@ func resourceAwsSesDomainIdentityCreate(d *schema.ResourceData, meta interface{} conn := meta.(*AWSClient).sesconn domainName := d.Get("domain").(string) - domainName = strings.TrimSuffix(domainName, ".") createOpts := &ses.VerifyDomainIdentityInput{ Domain: aws.String(domainName), diff --git a/aws/resource_aws_ses_domain_identity_test.go b/aws/resource_aws_ses_domain_identity_test.go index 60c7991de10..d9d2de3abfc 100644 --- a/aws/resource_aws_ses_domain_identity_test.go +++ b/aws/resource_aws_ses_domain_identity_test.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "regexp" "strings" "testing" @@ -109,6 +110,8 @@ func TestAccAWSSESDomainIdentity_disappears(t *testing.T) { }) } +// TestAccAWSSESDomainIdentity_trailingPeriod updated in 3.0 to account for domain plan-time validation +// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/13510 func TestAccAWSSESDomainIdentity_trailingPeriod(t *testing.T) { domain := fmt.Sprintf( "%s.terraformtesting.com.", @@ -120,11 +123,8 @@ func TestAccAWSSESDomainIdentity_trailingPeriod(t *testing.T) { CheckDestroy: testAccCheckAwsSESDomainIdentityDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsSESDomainIdentityConfig(domain), - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsSESDomainIdentityExists("aws_ses_domain_identity.test"), - testAccCheckAwsSESDomainIdentityArn("aws_ses_domain_identity.test", domain), - ), + Config: testAccAwsSESDomainIdentityConfig(domain), + ExpectError: regexp.MustCompile(`config is invalid: invalid value for domain \(cannot end with a period\)`), }, }, }) diff --git a/aws/resource_aws_ses_domain_identity_verification.go b/aws/resource_aws_ses_domain_identity_verification.go index a14d0424fd0..b1086b38f27 100644 --- a/aws/resource_aws_ses_domain_identity_verification.go +++ b/aws/resource_aws_ses_domain_identity_verification.go @@ -3,7 +3,7 @@ package aws import ( "fmt" "log" - "strings" + "regexp" "time" "github.com/aws/aws-sdk-go/aws" @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/ses" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) func resourceAwsSesDomainIdentityVerification() *schema.Resource { @@ -25,12 +26,10 @@ func resourceAwsSesDomainIdentityVerification() *schema.Resource { Computed: true, }, "domain": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: func(v interface{}) string { - return strings.TrimSuffix(v.(string), ".") - }, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, }, Timeouts: &schema.ResourceTimeout{ @@ -56,7 +55,7 @@ func getAwsSesIdentityVerificationAttributes(conn *ses.SES, domainName string) ( func resourceAwsSesDomainIdentityVerificationCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sesconn - domainName := strings.TrimSuffix(d.Get("domain").(string), ".") + domainName := d.Get("domain").(string) err := resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError { att, err := getAwsSesIdentityVerificationAttributes(conn, domainName) if err != nil { diff --git a/website/docs/guides/version-3-upgrade.html.md b/website/docs/guides/version-3-upgrade.html.md index 09fd362d262..89f4d82021c 100644 --- a/website/docs/guides/version-3-upgrade.html.md +++ b/website/docs/guides/version-3-upgrade.html.md @@ -23,6 +23,8 @@ Upgrade topics: - [Provider Custom Service Endpoint Updates](#provider-custom-service-endpoint-updates) - [Data Source: aws_availability_zones](#data-source-aws_availability_zones) - [Data Source: aws_lambda_invocation](#data-source-aws_lambda_invocation) +- [Data Source: aws_route53_resolver_rule](#data-source-aws_route53_resolver_rule) +- [Data Source: aws_route53_zone](#data-source-aws_route53_zone) - [Resource: aws_acm_certificate](#resource-aws_acm_certificate) - [Resource: aws_api_gateway_method_settings](#resource-aws_api_gateway_method_settings) - [Resource: aws_autoscaling_group](#resource-aws_autoscaling_group) @@ -42,6 +44,8 @@ Upgrade topics: - [Resource: aws_lb_listener_rule](#resource-aws_lb_listener_rule) - [Resource: aws_msk_cluster](#resource-aws_msk_cluster) - [Resource: aws_rds_cluster](#resource-aws_rds_cluster) +- [Resource: aws_route53_resolver_rule](#resource-aws_route53_resolver_rule) +- [Resource: aws_route53_zone](#resource-aws_route53_zone) - [Resource: aws_s3_bucket](#resource-aws_s3_bucket) - [Resource: aws_s3_bucket_metric](#resource-aws_s3_bucket_metric) - [Resource: aws_security_group](#resource-aws_security_group) @@ -209,6 +213,20 @@ output "lambda_result" { } ``` +## Data Source: aws_route53_resolver_rule + +### Removal of trailing period in domain_name argument + +Previously the data-source returned the Resolver Rule Domain Name directly from the API, which included a `.` suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g. ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Resolver Rule Domain Name such as `example.com.`, the attribute now will be returned as `example.com`. +While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted. + +## Data Source: aws_route53_zone + +### Removal of trailing period in name argument + +Previously the data-source returned the Hosted Zone Domain Name directly from the API, which included a `.` suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g. ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Hosted Zone Domain Name such as `example.com.`, the attribute now will be returned as `example.com`. +While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted. + ## Resource: aws_acm_certificate ### domain_validation_options Changed from List to Set @@ -1020,6 +1038,20 @@ resource "aws_msk_cluster" "example" { Previously when the `min_capacity` argument in a `scaling_configuration` block was not configured, the resource would default to 2. This behavior has been updated to align with the AWS RDS Cluster API default of 1. +## Resource: aws_route53_resolver_rule + +### Removal of trailing period in domain_name argument + +Previously the resource returned the Resolver Rule Domain Name directly from the API, which included a `.` suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g. ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Resolver Rule Domain Name such as `example.com.`, the attribute now will be returned as `example.com`. +While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted. + +## Resource: aws_route53_zone + +### Removal of trailing period in name argument + +Previously the resource returned the Hosted Zone Domain Name directly from the API, which included a `.` suffix. This proves difficult when many other AWS services do not accept this trailing period (e.g. ACM Certificate). This period is now automatically removed. For example, when the attribute would previously return a Hosted Zone Domain Name such as `example.com.`, the attribute now will be returned as `example.com`. +While the returned value will omit the trailing period, use of configurations with trailing periods will not be interrupted. + ## Resource: aws_s3_bucket ### Removal of Automatic aws_s3_bucket_policy Import