From 709b581c410eea3486204184da8e849995ffe7dc Mon Sep 17 00:00:00 2001 From: Angie Pinilla Date: Wed, 29 Jul 2020 18:41:00 -0400 Subject: [PATCH] CR updates to remove custom period handling and add validation instead --- aws/resource_aws_acm_certificate.go | 29 ++++--- aws/resource_aws_acm_certificate_test.go | 78 ++++++++----------- aws/resource_aws_route53_record.go | 9 ++- aws/resource_aws_route53_record_test.go | 27 ++++--- aws/resource_aws_ses_domain_identity.go | 12 +-- aws/resource_aws_ses_domain_identity_test.go | 10 +-- ...ce_aws_ses_domain_identity_verification.go | 10 ++- website/docs/guides/version-3-upgrade.html.md | 5 -- 8 files changed, 85 insertions(+), 95 deletions(-) diff --git a/aws/resource_aws_acm_certificate.go b/aws/resource_aws_acm_certificate.go index 447c7131d6b..699e8fb93f7 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" @@ -58,14 +59,14 @@ func resourceAwsAcmCertificate() *schema.Resource { }, "domain_name": { // AWS Provider 3.0.0 aws_route53_zone references no longer contain a - // trailing period, yet to account for custom user input, a StateFunc - // is in place to prevent ACM API error + // 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: trimTrailingPeriod, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, "subject_alternative_names": { Type: schema.TypeSet, @@ -73,11 +74,13 @@ func resourceAwsAcmCertificate() *schema.Resource { Computed: true, ForceNew: true, Elem: &schema.Schema{ - Type: schema.TypeString, // AWS Provider 3.0.0 aws_route53_zone references no longer contain a - // trailing period, yet to account for custom user input, a StateFunc - // is in place to prevent ACM API error - StateFunc: trimTrailingPeriod, + // trailing period, no longer requiring a custom StateFunc + // to prevent ACM API error + Type: schema.TypeString, + ValidateFunc: validation.All( + validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), + ), }, Set: schema.HashString, ConflictsWith: []string{"private_key", "certificate_body", "certificate_chain"}, @@ -162,7 +165,7 @@ 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), "."), + "domain_name": diff.Get("domain_name").(string), }} if sanSet, ok := diff.Get("subject_alternative_names").(*schema.Set); ok { @@ -225,7 +228,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(trimTrailingPeriod(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{})), } @@ -281,9 +284,7 @@ func resourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) err return resource.NonRetryableError(fmt.Errorf("Error describing certificate: %s", err)) } - // To be consistent with other AWS services that do not accept a trailing period, - // we remove the suffix from the Fully Qualified Domain Name of the Certificate returned from the API - d.Set("domain_name", trimTrailingPeriod(aws.StringValue(resp.Certificate.DomainName))) + d.Set("domain_name", resp.Certificate.DomainName) d.Set("arn", resp.Certificate.CertificateArn) d.Set("certificate_authority_arn", resp.Certificate.CertificateAuthorityArn) @@ -389,9 +390,7 @@ func convertValidationOptions(certificate *acm.CertificateDetail) ([]map[string] for _, o := range certificate.DomainValidationOptions { if o.ResourceRecord != nil { validationOption := map[string]interface{}{ - // To be consistent with other AWS services that do not accept a trailing period, - // we remove the suffix from the Fully Qualified Domain Name of the Certificate returned from the API - "domain_name": trimTrailingPeriod(aws.StringValue(o.DomainName)), + "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), diff --git a/aws/resource_aws_acm_certificate_test.go b/aws/resource_aws_acm_certificate_test.go index 4391a56b391..29635573ddc 100644 --- a/aws/resource_aws_acm_certificate_test.go +++ b/aws/resource_aws_acm_certificate_test.go @@ -130,7 +130,7 @@ func TestAccAWSAcmCertificate_emailValidation(t *testing.T) { Config: testAccAcmCertificateConfig(domain, acm.ValidationMethodEmail), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(domain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "0"), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), @@ -162,10 +162,10 @@ func TestAccAWSAcmCertificate_dnsValidation(t *testing.T) { Config: testAccAcmCertificateConfig(domain, acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(domain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(domain), + "domain_name": domain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -196,10 +196,10 @@ func TestAccAWSAcmCertificate_root(t *testing.T) { Config: testAccAcmCertificateConfig(rootDomain, acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(rootDomain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", rootDomain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(rootDomain), + "domain_name": rootDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -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", trimTrailingPeriod(domain)), - resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), - tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(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\)`), }, }, }) @@ -298,19 +282,19 @@ func TestAccAWSAcmCertificate_rootAndWildcardSan(t *testing.T) { Config: testAccAcmCertificateConfig_subjectAlternativeNames(rootDomain, strconv.Quote(wildcardDomain), acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(rootDomain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", rootDomain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "2"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(rootDomain), + "domain_name": rootDomain, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(wildcardDomain), + "domain_name": wildcardDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), - tfawsresource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", trimTrailingPeriod(wildcardDomain)), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), ), @@ -339,19 +323,19 @@ func TestAccAWSAcmCertificate_san_single(t *testing.T) { Config: testAccAcmCertificateConfig_subjectAlternativeNames(domain, strconv.Quote(sanDomain), acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(domain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "2"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(domain), + "domain_name": domain, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(sanDomain), + "domain_name": sanDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), - tfawsresource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", trimTrailingPeriod(sanDomain)), + tfawsresource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", sanDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), ), @@ -381,18 +365,18 @@ func TestAccAWSAcmCertificate_san_multiple(t *testing.T) { Config: testAccAcmCertificateConfig_subjectAlternativeNames(domain, fmt.Sprintf("%q, %q", sanDomain1, sanDomain2), acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(domain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "3"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(domain), + "domain_name": domain, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(sanDomain1), + "domain_name": sanDomain1, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(sanDomain2), + "domain_name": sanDomain2, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -427,14 +411,14 @@ func TestAccAWSAcmCertificate_san_TrailingPeriod(t *testing.T) { Config: testAccAcmCertificateConfig_subjectAlternativeNames(domain, strconv.Quote(sanDomain), acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile(`certificate/.+`)), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(domain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", domain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "2"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(domain), + "domain_name": domain, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(sanDomain), + "domain_name": strings.TrimSuffix(sanDomain, "."), "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -467,10 +451,10 @@ func TestAccAWSAcmCertificate_wildcard(t *testing.T) { Config: testAccAcmCertificateConfig(wildcardDomain, acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(wildcardDomain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(wildcardDomain), + "domain_name": wildcardDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -502,14 +486,14 @@ func TestAccAWSAcmCertificate_wildcardAndRootSan(t *testing.T) { Config: testAccAcmCertificateConfig_subjectAlternativeNames(wildcardDomain, strconv.Quote(rootDomain), acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(wildcardDomain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "2"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(rootDomain), + "domain_name": rootDomain, "resource_record_type": "CNAME", }), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(wildcardDomain), + "domain_name": wildcardDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), @@ -541,10 +525,10 @@ func TestAccAWSAcmCertificate_disableCTLogging(t *testing.T) { Config: testAccAcmCertificateConfig_disableCTLogging(rootDomain, acm.ValidationMethodDns), Check: resource.ComposeTestCheckFunc( testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), - resource.TestCheckResourceAttr(resourceName, "domain_name", trimTrailingPeriod(rootDomain)), + resource.TestCheckResourceAttr(resourceName, "domain_name", rootDomain), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "1"), tfawsresource.TestCheckTypeSetElemNestedAttrs(resourceName, "domain_validation_options.*", map[string]string{ - "domain_name": trimTrailingPeriod(rootDomain), + "domain_name": rootDomain, "resource_record_type": "CNAME", }), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), diff --git a/aws/resource_aws_route53_record.go b/aws/resource_aws_route53_record.go index 4b96ffbbadd..ab70637512e 100644 --- a/aws/resource_aws_route53_record.go +++ b/aws/resource_aws_route53_record.go @@ -41,9 +41,12 @@ func resourceAwsRoute53Record() *schema.Resource { Required: true, ForceNew: true, StateFunc: func(v interface{}) string { - value := trimTrailingPeriod(v) - return strings.ToLower(value) + // AWS Provider 3.0.0 aws_route53_zone references no longer contain a + // trailing period, no longer requiring the custom StateFunc + // to trim the string to prevent Route53 API error + return strings.ToLower(v.(string)) }, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, "fqdn": { @@ -919,7 +922,7 @@ func cleanRecordName(name string) string { // If it does not, add the zone name to form a fully qualified name // and keep AWS happy. func expandRecordName(name, zone string) string { - rn := strings.ToLower(trimTrailingPeriod(name)) + rn := strings.ToLower(name) zone = strings.TrimSuffix(zone, ".") if !strings.HasSuffix(rn, zone) { if len(name) == 0 { diff --git a/aws/resource_aws_route53_record_test.go b/aws/resource_aws_route53_record_test.go index 52dfcec4caa..ac729ca4e9b 100644 --- a/aws/resource_aws_route53_record_test.go +++ b/aws/resource_aws_route53_record_test.go @@ -205,7 +205,7 @@ func TestAccAWSRoute53Record_disappears_MultipleRecords(t *testing.T) { } func TestAccAWSRoute53Record_basic_fqdn(t *testing.T) { - var record1, record2 route53.ResourceRecordSet + var record1 route53.ResourceRecordSet resourceName := "aws_route53_record.default" resource.ParallelTest(t, resource.TestCase{ @@ -226,18 +226,23 @@ func TestAccAWSRoute53Record_basic_fqdn(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"allow_overwrite", "weight"}, }, + }, + }) +} + +// Reference: https://github.com/terraform-providers/terraform-provider-aws/issues/13510 +func TestAccAWSRoute53Record_basic_name_trailingPeriod(t *testing.T) { + resourceName := "aws_route53_record.default" - // Ensure that changing the name to include a trailing "dot" results in - // nothing happening, because the name is stripped of trailing dots on - // save. Otherwise, an update would occur and due to the - // create_before_destroy, the record would actually be destroyed, and a - // non-empty plan would appear, and the record will fail to exist in - // testAccCheckRoute53RecordExists + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + Providers: testAccProviders, + CheckDestroy: testAccCheckRoute53RecordDestroy, + Steps: []resource.TestStep{ { - Config: testAccRoute53RecordConfig_fqdn_no_op, - Check: resource.ComposeTestCheckFunc( - testAccCheckRoute53RecordExists(resourceName, &record2), - ), + Config: testAccRoute53RecordConfig_fqdn_no_op, + ExpectError: regexp.MustCompile(`config is invalid: invalid value for name \(cannot end with a period\)`), }, }, }) diff --git a/aws/resource_aws_ses_domain_identity.go b/aws/resource_aws_ses_domain_identity.go index 53b6da85778..3ec2813e7ee 100644 --- a/aws/resource_aws_ses_domain_identity.go +++ b/aws/resource_aws_ses_domain_identity.go @@ -3,11 +3,13 @@ package aws import ( "fmt" "log" + "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 { @@ -25,10 +27,10 @@ func resourceAwsSesDomainIdentity() *schema.Resource { Computed: true, }, "domain": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: trimTrailingPeriod, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, "verification_token": { Type: schema.TypeString, @@ -41,7 +43,7 @@ func resourceAwsSesDomainIdentity() *schema.Resource { func resourceAwsSesDomainIdentityCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).sesconn - domainName := trimTrailingPeriod(d.Get("domain").(string)) + domainName := d.Get("domain").(string) 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 48afeae96c6..b1086b38f27 100644 --- a/aws/resource_aws_ses_domain_identity_verification.go +++ b/aws/resource_aws_ses_domain_identity_verification.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "regexp" "time" "github.com/aws/aws-sdk-go/aws" @@ -10,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 { @@ -24,10 +26,10 @@ func resourceAwsSesDomainIdentityVerification() *schema.Resource { Computed: true, }, "domain": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - StateFunc: trimTrailingPeriod, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringDoesNotMatch(regexp.MustCompile(`\.$`), "cannot end with a period"), }, }, Timeouts: &schema.ResourceTimeout{ diff --git a/website/docs/guides/version-3-upgrade.html.md b/website/docs/guides/version-3-upgrade.html.md index 3ae9d5b3d1e..89f4d82021c 100644 --- a/website/docs/guides/version-3-upgrade.html.md +++ b/website/docs/guides/version-3-upgrade.html.md @@ -229,11 +229,6 @@ While the returned value will omit the trailing period, use of configurations wi ## Resource: aws_acm_certificate -### Removal of trailing period in domain_name argument and domain_validation_options.domain_name attribute - -Previously the resource returned the Fully Qualified Domain Name of the certificate directly from the API, which included a `.` suffix. This proves difficult when many other AWS services do not accept this trailing period. This period is now automatically removed. For example, when `domain_name` or `domain_validation_options.domain_name` would previously return a Fully Qualified Domain Name such as `example.com.`, it now will be returned as `example.com`. -While the returned value for the `domain_name` argument will omit the trailing period, use of configurations with the trailing period in will not be interrupted. - ### domain_validation_options Changed from List to Set Previously, the `domain_validation_options` attribute was a list type and completely unknown until after an initial `terraform apply`. This generally required complicated configuration workarounds to properly create DNS validation records since referencing this attribute directly could produce errors similar to the below: