Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resources: aws_acm_certificate and aws_acm_certificate_validation #2813

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a74249c
Add basic aws_acm_certificate resource
flosell Dec 31, 2017
4e49e55
Add basic aws_acm_certificate_validation resource that waits until a …
flosell Dec 31, 2017
d4ef13a
Add documentation for aws_acm_certificate and aws_acm_certificate_val…
flosell Jan 13, 2018
fc359ec
r/aws_acm_certificate_validation: Add sanity checks
flosell Jan 14, 2018
b1bd46b
r/aws_acm_certificate_validation: Test and clean up support for SANs
flosell Jan 14, 2018
fe230cb
r/aws_acm_certificate: Add ability to import certificates
flosell Jan 20, 2018
bf646ab
r/aws_acm_certificate_validation: Ignore trailing . when validating F…
flosell Jan 20, 2018
32a9137
r/aws_acm_certificate: Validate validation_method
flosell Jan 20, 2018
fa7bc0a
r/aws_acm_certificate: Don't set certificate_arn twice
flosell Jan 20, 2018
259ced4
r/aws_acm_certificate: Add support for tagging
flosell Jan 20, 2018
d81ab23
r/aws_acm_certificate: Make domain name used in acceptance tests conf…
flosell Jan 20, 2018
3afd4fb
Adjust code in documentation to the spec
joelhandwell Jan 30, 2018
65c85fe
r/aws_acm_certificate: Improve code style by organising imports, usi…
flosell Feb 1, 2018
f3b4d69
r/aws_acm_certificate: Change certificate_arn attribute to arn
flosell Feb 1, 2018
77fd92d
r/aws_acm_certificate: Remove error checks for setting string attributes
flosell Feb 1, 2018
ee046e8
r/aws_acm_certificate: Remove uselesss describe-call in delete operation
flosell Feb 1, 2018
010da87
r/aws_acm_certificate: Randomize domains used in acceptance test to b…
flosell Feb 2, 2018
263d9b1
r/aws_acm_certificate: Add resource attribute checks to tests to make…
flosell Feb 2, 2018
2f6219b
r/aws_acm_certificate and r/aws_acm_certificate_validation: Clean up …
flosell Feb 2, 2018
0a4b196
r/aws_acm_certificate_validation: Remove attribute reference since it…
flosell Feb 2, 2018
026193a
r/aws_acm_certificate: Be more tolerant of certificates deleted outsi…
flosell Feb 2, 2018
b37bf16
r/aws_acm_certificate: Add support for EMAIL validation
flosell Feb 6, 2018
88a8595
r/aws_acm_certificate: Refactor if-statement for readability
flosell Feb 6, 2018
533badc
r/aws_acm_certificate: Simplify tests, rely on resource state instead…
flosell Feb 6, 2018
d80b669
r/aws_acm_certificate: Remove unused function
flosell Feb 6, 2018
d4c01e1
r/aws_acm_certificate: Simplify tests, parameterize instead of duplicate
flosell Feb 6, 2018
ff443c1
r/aws_acm_certificate and r/aws_acm_certificate_validation: Split tes…
flosell Feb 6, 2018
e42421a
r/aws_acm_certificate_validation: Use built-in timeout functionality …
flosell Feb 6, 2018
75d784c
r/aws_acm_certificate: Allow importing of certificates that aren't AM…
flosell Feb 6, 2018
b2c070c
r/aws_acm_certificate: Add warning about importing imported certifica…
flosell Feb 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"aws_acm_certificate": resourceAwsAcmCertificate(),
"aws_acm_certificate_validation": resourceAwsAcmCertificateValidation(),
"aws_ami": resourceAwsAmi(),
"aws_ami_copy": resourceAwsAmiCopy(),
"aws_ami_from_instance": resourceAwsAmiFromInstance(),
Expand Down
244 changes: 244 additions & 0 deletions aws/resource_aws_acm_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package aws

import (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: We try to keep the standard packages up top and third party below in the imports. Most of the maintainers run goimports to automatically organize (and add/remove!) the imports. Its pretty awesome if you haven't tried it, especially as a post-save hook in your editor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks for the pointers to goimports, that helps a lot!

"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/acm"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsAcmCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAcmCertificateCreate,
Read: resourceAwsAcmCertificateRead,
Update: resourceAwsAcmCertificateUpdate,
Delete: resourceAwsAcmCertificateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add status to the resource's schema as a computed string attribute please? This will make acceptance testing a little easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it out, looks like this leads to some interesting interactions: When using the fully automatic validation flow (e.g. with route53 and the acm_certificate_validation resource), the acm_certificate resource completes first (with a PENDING_VALIDATION status), then the route53 record gets added and acm_certificate_validation waits until ACM issues the certificate. Since the acm_certificate resource isn't refreshed at that point, it stays in status PENDING_VALIDATION. After the next terraform run, this state switches to ISSUED.

I haven't seen an immediate impact but I'd guess this can lead to some interesting surprises down the road :)

"domain_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"subject_alternative_names": {
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"validation_method": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"arn": {
Type: schema.TypeString,
Computed: true,
},
"domain_validation_options": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"domain_name": {
Type: schema.TypeString,
Computed: true,
},
"resource_record_name": {
Type: schema.TypeString,
Computed: true,
},
"resource_record_type": {
Type: schema.TypeString,
Computed: true,
},
"resource_record_value": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"validation_emails": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"tags": tagsSchema(),
},
}
}

func resourceAwsAcmCertificateCreate(d *schema.ResourceData, meta interface{}) error {
acmconn := meta.(*AWSClient).acmconn
params := &acm.RequestCertificateInput{
DomainName: aws.String(d.Get("domain_name").(string)),
ValidationMethod: aws.String(d.Get("validation_method").(string)),
}

sans, ok := d.GetOk("subject_alternative_names")
if ok {
sanStrings := sans.([]interface{})
params.SubjectAlternativeNames = expandStringList(sanStrings)
}

log.Printf("[DEBUG] ACM Certificate Request: %#v", params)
resp, err := acmconn.RequestCertificate(params)

if err != nil {
return fmt.Errorf("Error requesting certificate: %s", err)
}

d.SetId(*resp.CertificateArn)
if v, ok := d.GetOk("tags"); ok {
params := &acm.AddTagsToCertificateInput{
CertificateArn: resp.CertificateArn,
Tags: tagsFromMapACM(v.(map[string]interface{})),
}
_, err := acmconn.AddTagsToCertificate(params)

if err != nil {
return fmt.Errorf("Error requesting certificate: %s", err)
}
}

return resourceAwsAcmCertificateRead(d, meta)
}

func resourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) error {
acmconn := meta.(*AWSClient).acmconn

params := &acm.DescribeCertificateInput{
CertificateArn: aws.String(d.Id()),
}

return resource.Retry(time.Duration(1)*time.Minute, func() *resource.RetryError {
resp, err := acmconn.DescribeCertificate(params)

if err != nil {
if isAWSErr(err, acm.ErrCodeResourceNotFoundException, "") {
d.SetId("")
return nil
}
return resource.NonRetryableError(fmt.Errorf("Error describing certificate: %s", err))
}

d.Set("domain_name", resp.Certificate.DomainName)
d.Set("arn", resp.Certificate.CertificateArn)

if err := d.Set("subject_alternative_names", cleanUpSubjectAlternativeNames(resp.Certificate)); err != nil {
return resource.NonRetryableError(err)
}

domainValidationOptions, emailValidationOptions, err := convertValidationOptions(resp.Certificate)

if err != nil {
return resource.RetryableError(err)
}

if err := d.Set("domain_validation_options", domainValidationOptions); err != nil {
return resource.NonRetryableError(err)
}
if err := d.Set("validation_emails", emailValidationOptions); err != nil {
return resource.NonRetryableError(err)
}
d.Set("validation_method", resourceAwsAcmCertificateGuessValidationMethod(domainValidationOptions, emailValidationOptions))

params := &acm.ListTagsForCertificateInput{
CertificateArn: aws.String(d.Id()),
}

tagResp, err := acmconn.ListTagsForCertificate(params)
if err := d.Set("tags", tagsToMapACM(tagResp.Tags)); err != nil {
return resource.NonRetryableError(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 resourceAwsAcmCertificateUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange("tags") {
acmconn := meta.(*AWSClient).acmconn
err := setTagsACM(acmconn, d)
if err != nil {
return err
}
}
return nil
}

func cleanUpSubjectAlternativeNames(cert *acm.CertificateDetail) []string {
sans := cert.SubjectAlternativeNames
vs := make([]string, 0, len(sans)-1)
for _, v := range sans {
if *v != *cert.DomainName {
vs = append(vs, *v)
}
}
return vs

}

func convertValidationOptions(certificate *acm.CertificateDetail) ([]map[string]interface{}, []string, error) {
var domainValidationResult []map[string]interface{}
var emailValidationResult []string

if *certificate.Type == acm.CertificateTypeAmazonIssued {
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,
}
domainValidationResult = append(domainValidationResult, validationOption)
} else if o.ValidationEmails != nil && len(o.ValidationEmails) > 0 {
for _, validationEmail := range o.ValidationEmails {
emailValidationResult = append(emailValidationResult, *validationEmail)
}
} else {
log.Printf("[DEBUG] No validation options need to retry: %#v", o)
return nil, nil, fmt.Errorf("No validation options need to retry: %#v", o)
}
}
}

return domainValidationResult, emailValidationResult, nil
}

func resourceAwsAcmCertificateDelete(d *schema.ResourceData, meta interface{}) error {
acmconn := meta.(*AWSClient).acmconn

params := &acm.DeleteCertificateInput{
CertificateArn: aws.String(d.Id()),
}

_, err := acmconn.DeleteCertificate(params)

if err != nil && !isAWSErr(err, acm.ErrCodeResourceNotFoundException, "") {
return fmt.Errorf("Error deleting certificate: %s", err)
}

d.SetId("")
return nil
}
Loading