From e47e87875cd6d7f21262a1177ff275796d4586c0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Apr 2022 15:20:38 -0400 Subject: [PATCH] r/aws_acm_certificate: Add 'validation_option' argument. --- .changelog/3853.txt | 3 + internal/service/acm/certificate.go | 68 ++++++++++++++++++++ internal/service/acm/certificate_test.go | 60 ++++++++++++++++- website/docs/r/acm_certificate.html.markdown | 22 +++++++ 4 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 .changelog/3853.txt diff --git a/.changelog/3853.txt b/.changelog/3853.txt new file mode 100644 index 000000000000..c860222c1670 --- /dev/null +++ b/.changelog/3853.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_acm_certificate: Add `validation_option` argument +``` \ No newline at end of file diff --git a/internal/service/acm/certificate.go b/internal/service/acm/certificate.go index 65ff3826f80e..b5999d419ff5 100644 --- a/internal/service/acm/certificate.go +++ b/internal/service/acm/certificate.go @@ -177,6 +177,26 @@ func ResourceCertificate() *schema.Resource { ValidateFunc: validation.StringInSlice(append(acm.ValidationMethod_Values(), certificateValidationMethodNone), false), ConflictsWith: []string{"certificate_authority_arn", "certificate_body", "certificate_chain", "private_key"}, }, + "validation_option": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "validation_domain": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + }, + ConflictsWith: []string{"certificate_body", "certificate_chain", "private_key"}, + }, }, CustomizeDiff: customdiff.Sequence( @@ -271,6 +291,10 @@ func resourceCertificateCreate(d *schema.ResourceData, meta interface{}) error { input.ValidationMethod = aws.String(v.(string)) } + if v, ok := d.GetOk("validation_option"); ok && v.(*schema.Set).Len() > 0 { + input.DomainValidationOptions = expandDomainValidationOptions(v.(*schema.Set).List()) + } + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } @@ -486,6 +510,50 @@ func flattenCertificateOptions(apiObject *acm.CertificateOptions) map[string]int return tfMap } +func expandDomainValidationOption(tfMap map[string]interface{}) *acm.DomainValidationOption { + if tfMap == nil { + return nil + } + + apiObject := &acm.DomainValidationOption{} + + if v, ok := tfMap["domain_name"].(string); ok && v != "" { + apiObject.DomainName = aws.String(v) + } + + if v, ok := tfMap["validation_domain"].(string); ok && v != "" { + apiObject.ValidationDomain = aws.String(v) + } + + return apiObject +} + +func expandDomainValidationOptions(tfList []interface{}) []*acm.DomainValidationOption { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*acm.DomainValidationOption + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandDomainValidationOption(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + func flattenDomainValidation(apiObject *acm.DomainValidation) (map[string]interface{}, []string) { if apiObject == nil { return nil, nil diff --git a/internal/service/acm/certificate_test.go b/internal/service/acm/certificate_test.go index d78e2317511c..73211c2be65b 100644 --- a/internal/service/acm/certificate_test.go +++ b/internal/service/acm/certificate_test.go @@ -41,6 +41,7 @@ func TestAccACMCertificate_emailValidation(t *testing.T) { acctest.CheckResourceAttrGreaterThanValue(resourceName, "validation_emails.#", "0"), resource.TestMatchResourceAttr(resourceName, "validation_emails.0", regexp.MustCompile(`^[^@]+@.+$`)), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodEmail), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -80,6 +81,7 @@ func TestAccACMCertificate_dnsValidation(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -118,6 +120,7 @@ func TestAccACMCertificate_root(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", rootDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), ), }, { @@ -129,6 +132,44 @@ func TestAccACMCertificate_root(t *testing.T) { }) } +func TestAccACMCertificate_validationOptions(t *testing.T) { + resourceName := "aws_acm_certificate.test" + rootDomain := acctest.ACMCertificateDomainFromEnv(t) + domain := acctest.ACMCertificateRandomSubDomain(rootDomain) + var v acm.CertificateDetail + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, acm.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckAcmCertificateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAcmCertificateValidationOptionsConfig(rootDomain, domain), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAcmCertificateExists(resourceName, &v), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), + 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.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", domain), + acctest.CheckResourceAttrGreaterThanValue(resourceName, "validation_emails.#", "0"), + resource.TestMatchResourceAttr(resourceName, "validation_emails.0", regexp.MustCompile(`^[^@]+@.+$`)), + resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodEmail), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"validation_option"}, + }, + }, + }) +} + func TestAccACMCertificate_privateCert(t *testing.T) { certificateAuthorityResourceName := "aws_acmpca_certificate_authority.test" resourceName := "aws_acm_certificate.test" @@ -154,6 +195,7 @@ func TestAccACMCertificate_privateCert(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "subject_alternative_names.*", certificateDomainName), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", "NONE"), + resource.TestCheckResourceAttr(resourceName, "validation_option.#", "0"), resource.TestCheckResourceAttrPair(resourceName, "certificate_authority_arn", certificateAuthorityResourceName, "arn"), ), }, @@ -750,12 +792,26 @@ func testAccCheckAcmCertificateDestroy(s *terraform.State) error { func testAccAcmCertificateConfig(domainName, validationMethod string) string { return fmt.Sprintf(` resource "aws_acm_certificate" "test" { - domain_name = "%s" - validation_method = "%s" + domain_name = %[1]q + validation_method = %[2]q } `, domainName, validationMethod) } +func testAccAcmCertificateValidationOptionsConfig(rootDomainName, domainName string) string { + return fmt.Sprintf(` +resource "aws_acm_certificate" "test" { + domain_name = %[2]q + validation_method = "EMAIL" + + validation_option { + domain_name = %[2]q + validation_domain = %[1]q + } +} +`, rootDomainName, domainName) +} + func testAccAcmCertificatePrivateCertConfig(commonName, certificateDomainName string) string { return fmt.Sprintf(` resource "aws_acmpca_certificate_authority" "test" { diff --git a/website/docs/r/acm_certificate.html.markdown b/website/docs/r/acm_certificate.html.markdown index 2211c6a2fee2..1a532f9ec344 100644 --- a/website/docs/r/acm_certificate.html.markdown +++ b/website/docs/r/acm_certificate.html.markdown @@ -45,6 +45,20 @@ resource "aws_acm_certificate" "cert" { } ``` +### Custom Domain Validation Options + +```terraform +resource "aws_acm_certificate" "cert" { + domain_name = "testing.example.com" + validation_method = "EMAIL" + + validation_option { + domain_name = "testing.example.com" + validation_domain = "example.com" + } +} +``` + ### Existing Certificate Body Import ```terraform @@ -108,6 +122,7 @@ The following arguments are supported: * `subject_alternative_names` - (Optional) Set of domains that should be SANs in the issued certificate. To remove all elements of a previously configured list, set this value equal to an empty list (`[]`) or use the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html) to trigger recreation. * `validation_method` - (Required) Which method to use for validation. `DNS` or `EMAIL` are valid, `NONE` can be used for certificates that were imported into ACM and then into Terraform. * `options` - (Optional) Configuration block used to set certificate options. Detailed below. + * `validation_option` - (Optional) Configuration block used to specify information about the initial validation of each domain name. Detailed below. * Importing an existing certificate * `private_key` - (Required) The certificate's PEM-formatted private key * `certificate_body` - (Required) The certificate's PEM-formatted public key @@ -124,6 +139,13 @@ Supported nested arguments for the `options` configuration block: * `certificate_transparency_logging_preference` - (Optional) Specifies whether certificate details should be added to a certificate transparency log. Valid values are `ENABLED` or `DISABLED`. See https://docs.aws.amazon.com/acm/latest/userguide/acm-concepts.html#concept-transparency for more details. +## validation_option Configuration Block + +Supported nested arguments for the `validation_option` configuration block: + +* `domain_name` - (Required) A fully qualified domain name (FQDN) in the certificate. +* `validation_domain` - (Required) The domain name that you want ACM to use to send you validation emails. This domain name is the suffix of the email addresses that you want ACM to use. This must be the same as the `domain_name` value or a superdomain of the `domain_name` value. For example, if you request a certificate for `"testing.example.com"`, you can specify `"example.com"` for this value. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: