diff --git a/aws/resource_aws_acm_certificate.go b/aws/resource_aws_acm_certificate.go index d020554e5410..a4fd78a2f23e 100644 --- a/aws/resource_aws_acm_certificate.go +++ b/aws/resource_aws_acm_certificate.go @@ -15,6 +15,16 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" ) +const ( + // Maximum amount of time for ACM Certificate cross-service reference propagation. + // Removal of ACM Certificates from API Gateway Custom Domains can take >15 minutes. + AcmCertificateCrossServicePropagationTimeout = 20 * time.Minute + + // Maximum amount of time for ACM Certificate asynchronous DNS validation record assignment. + // This timeout is unrelated to any creation or validation of those assigned DNS records. + AcmCertificateDnsValidationAssignmentTimeout = 5 * time.Minute +) + func resourceAwsAcmCertificate() *schema.Resource { return &schema.Resource{ Create: resourceAwsAcmCertificateCreate, @@ -143,6 +153,10 @@ func resourceAwsAcmCertificate() *schema.Resource { }, }, }, + "status": { + Type: schema.TypeString, + Computed: true, + }, "tags": tagsSchema(), }, } @@ -227,7 +241,7 @@ func resourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) err CertificateArn: aws.String(d.Id()), } - return resource.Retry(time.Duration(5)*time.Minute, func() *resource.RetryError { + return resource.Retry(AcmCertificateDnsValidationAssignmentTimeout, func() *resource.RetryError { resp, err := acmconn.DescribeCertificate(params) if err != nil { @@ -259,12 +273,14 @@ func resourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) err return resource.NonRetryableError(err) } - d.Set("validation_method", resourceAwsAcmCertificateGuessValidationMethod(domainValidationOptions, emailValidationOptions)) + d.Set("validation_method", resourceAwsAcmCertificateValidationMethod(resp.Certificate)) if err := d.Set("options", flattenAcmCertificateOptions(resp.Certificate.Options)); err != nil { return resource.NonRetryableError(fmt.Errorf("error setting certificate options: %s", err)) } + d.Set("status", resp.Certificate.Status) + tags, err := keyvaluetags.AcmListTags(acmconn, d.Id()) if err != nil { @@ -278,16 +294,16 @@ func resourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) err return nil }) } -func resourceAwsAcmCertificateGuessValidationMethod(domainValidationOptions []map[string]interface{}, emailValidationOptions []string) string { - // The DescribeCertificate Response doesn't have information on what validation method was used - // so we need to guess from the validation options we see... - if len(domainValidationOptions) > 0 { - return acm.ValidationMethodDns - } else if len(emailValidationOptions) > 0 { - return acm.ValidationMethodEmail - } else { - return "NONE" +func resourceAwsAcmCertificateValidationMethod(certificate *acm.CertificateDetail) string { + if aws.StringValue(certificate.Type) == acm.CertificateTypeAmazonIssued { + for _, domainValidation := range certificate.DomainValidationOptions { + if domainValidation.ValidationMethod != nil { + return aws.StringValue(domainValidation.ValidationMethod) + } + } } + + return "NONE" } func resourceAwsAcmCertificateUpdate(d *schema.ResourceData, meta interface{}) error { @@ -345,8 +361,8 @@ func convertValidationOptions(certificate *acm.CertificateDetail) ([]map[string] emailValidationResult = append(emailValidationResult, *validationEmail) } } else if o.ValidationStatus == nil || aws.StringValue(o.ValidationStatus) == acm.DomainStatusPendingValidation { - log.Printf("[DEBUG] No validation options need to retry: %#v", o) - return nil, nil, fmt.Errorf("No validation options need to retry: %#v", o) + log.Printf("[DEBUG] Asynchronous ACM service domain validation assignment not complete, need to retry: %#v", o) + return nil, nil, fmt.Errorf("asynchronous ACM service domain validation assignment not complete, need to retry: %#v", o) } } case acm.CertificateTypePrivate: @@ -369,7 +385,7 @@ func resourceAwsAcmCertificateDelete(d *schema.ResourceData, meta interface{}) e CertificateArn: aws.String(d.Id()), } - err := resource.Retry(10*time.Minute, func() *resource.RetryError { + err := resource.Retry(AcmCertificateCrossServicePropagationTimeout, func() *resource.RetryError { _, err := acmconn.DeleteCertificate(params) if err != nil { if isAWSErr(err, acm.ErrCodeResourceInUseException, "") { diff --git a/aws/resource_aws_acm_certificate_test.go b/aws/resource_aws_acm_certificate_test.go index 35e75fa40070..2f07b8861a5d 100644 --- a/aws/resource_aws_acm_certificate_test.go +++ b/aws/resource_aws_acm_certificate_test.go @@ -131,6 +131,7 @@ func TestAccAWSAcmCertificate_emailValidation(t *testing.T) { testAccMatchResourceAttrRegionalARN(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.#", "0"), resource.TestMatchResourceAttr(resourceName, "validation_emails.0", regexp.MustCompile(`^[^@]+@.+$`)), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodEmail), @@ -166,6 +167,7 @@ func TestAccAWSAcmCertificate_dnsValidation(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.0.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_value"), + 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), @@ -199,6 +201,7 @@ func TestAccAWSAcmCertificate_root(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.0.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_value"), + 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), @@ -229,6 +232,7 @@ func TestAccAWSAcmCertificate_privateCert(t *testing.T) { testAccMatchResourceAttrRegionalARN(resourceName, "arn", "acm", regexp.MustCompile("certificate/.+$")), resource.TestCheckResourceAttr(resourceName, "domain_name", fmt.Sprintf("%s.terraformtesting.com", rName)), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.#", "0"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusFailed), // FailureReason: PCA_INVALID_STATE (PCA State: PENDING_CERTIFICATE) resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", "NONE"), @@ -264,6 +268,7 @@ func TestAccAWSAcmCertificate_root_TrailingPeriod(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.0.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_value"), + 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), @@ -302,6 +307,7 @@ func TestAccAWSAcmCertificate_rootAndWildcardSan(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.1.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_value"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.0", wildcardDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), @@ -342,6 +348,7 @@ func TestAccAWSAcmCertificate_san_single(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.1.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_value"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.0", sanDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), @@ -387,6 +394,7 @@ func TestAccAWSAcmCertificate_san_multiple(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.2.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.2.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.2.resource_record_value"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "2"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.0", sanDomain1), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.1", sanDomain2), @@ -428,6 +436,7 @@ func TestAccAWSAcmCertificate_san_TrailingPeriod(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.1.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_value"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.0", strings.TrimSuffix(sanDomain, ".")), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), @@ -463,6 +472,7 @@ func TestAccAWSAcmCertificate_wildcard(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.0.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_value"), + 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), @@ -501,6 +511,7 @@ func TestAccAWSAcmCertificate_wildcardAndRootSan(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_name"), resource.TestCheckResourceAttr(resourceName, "domain_validation_options.1.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.1.resource_record_value"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "1"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.0", rootDomain), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), @@ -536,6 +547,7 @@ func TestAccAWSAcmCertificate_disableCTLogging(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "domain_validation_options.0.resource_record_type", "CNAME"), resource.TestCheckResourceAttrSet(resourceName, "domain_validation_options.0.resource_record_value"), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusPendingValidation), resource.TestCheckResourceAttr(resourceName, "validation_emails.#", "0"), resource.TestCheckResourceAttr(resourceName, "validation_method", acm.ValidationMethodDns), resource.TestCheckResourceAttr(resourceName, "options.#", "1"), @@ -611,6 +623,7 @@ func TestAccAWSAcmCertificate_imported_DomainName(t *testing.T) { { Config: testAccAcmCertificateConfigPrivateKey("example.com"), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusIssued), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "domain_name", "example.com"), ), @@ -618,6 +631,7 @@ func TestAccAWSAcmCertificate_imported_DomainName(t *testing.T) { { Config: testAccAcmCertificateConfigPrivateKey("example.org"), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusIssued), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "domain_name", "example.org"), ), @@ -646,6 +660,7 @@ func TestAccAWSAcmCertificate_imported_IpAddress(t *testing.T) { // Reference: h Config: testAccAcmCertificateConfigPrivateKey("1.2.3.4"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "domain_name", ""), + resource.TestCheckResourceAttr(resourceName, "status", acm.CertificateStatusIssued), resource.TestCheckResourceAttr(resourceName, "subject_alternative_names.#", "0"), ), }, diff --git a/website/docs/r/acm_certificate.html.markdown b/website/docs/r/acm_certificate.html.markdown index 70a13a08ea93..9a86ff678887 100644 --- a/website/docs/r/acm_certificate.html.markdown +++ b/website/docs/r/acm_certificate.html.markdown @@ -82,7 +82,7 @@ The following arguments are supported: * Creating an amazon issued certificate * `domain_name` - (Required) A domain name for which the certificate should be issued - * `subject_alternative_names` - (Optional) A list of domains that should be SANs in the issued certificate + * `subject_alternative_names` - (Optional) A list 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. * Importing an existing certificate @@ -92,7 +92,7 @@ The following arguments are supported: * Creating a private CA issued certificate * `domain_name` - (Required) A domain name for which the certificate should be issued * `certificate_authority_arn` - (Required) ARN of an ACMPCA - * `subject_alternative_names` - (Optional) A list of domains that should be SANs in the issued certificate + * `subject_alternative_names` - (Optional) A list 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. * `tags` - (Optional) A map of tags to assign to the resource. ## options Configuration Block @@ -109,6 +109,7 @@ In addition to all arguments above, the following attributes are exported: * `arn` - The ARN of the certificate * `domain_name` - The domain name for which the certificate is issued * `domain_validation_options` - A list of attributes to feed into other resources to complete certificate validation. Can have more than one element, e.g. if SANs are defined. Only set if `DNS`-validation was used. +* `status` - Status of the certificate. * `validation_emails` - A list of addresses that received a validation E-Mail. Only set if `EMAIL`-validation was used. Domain validation objects export the following attributes: