-
Notifications
You must be signed in to change notification settings - Fork 9.3k
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
Changes from 12 commits
a74249c
4e49e55
d4ef13a
fc359ec
b1bd46b
fe230cb
bf646ab
32a9137
fa7bc0a
259ced4
d81ab23
3afd4fb
65c85fe
f3b4d69
77fd92d
ee046e8
010da87
263d9b1
2f6219b
0a4b196
026193a
b37bf16
88a8595
533badc
d80b669
d4c01e1
ff443c1
e42421a
75d784c
b2c070c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"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" | ||
"log" | ||
"time" | ||
) | ||
|
||
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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I haven't seen an immediate impact but I'd guess this can lead to some interesting surprises down the road :) |
||
"domain_name": &schema.Schema{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"subject_alternative_names": &schema.Schema{ | ||
Type: schema.TypeList, | ||
Optional: true, | ||
ForceNew: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
}, | ||
"validation_method": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two things:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the constant now. Supporting E-Mail validation will probably add a bit more work and complexity. It's also hard to test. Since this PR is already pretty big, my initial idea was to add support for E-Mail validation in a separate PR later if there is demand for it. If DNS validation works smoothly, I'd guess most people (unless they can't for some reason) will use it and not bother with E-Mail. Does that make sense? If you prefer adding E-Mail support right now, I'd also be willing to invest a bit of time to get it in. |
||
if v.(string) != "DNS" { | ||
errors = append(errors, fmt.Errorf("only validation_method DNS is supported at the moment")) | ||
} | ||
return | ||
}, | ||
}, | ||
"certificate_arn": &schema.Schema{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two things about this attribute:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Type: schema.TypeString, | ||
Computed: true, | ||
ForceNew: true, | ||
}, | ||
"domain_validation_options": &schema.Schema{ | ||
Type: schema.TypeList, | ||
Computed: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"domain_name": &schema.Schema{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"resource_record_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"resource_record_type": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"resource_record_value": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"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("DNS"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: constant is available There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
} | ||
|
||
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 { | ||
return resource.NonRetryableError(fmt.Errorf("Error describing certificate: %s", err)) | ||
} | ||
|
||
if *resp.Certificate.Type != "AMAZON_ISSUED" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a technical reason for limiting this here? Is there some reason I wouldn't be able to import an Otherwise, nitpick: constant available There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mainly because I haven't tested it yet. I'm guessing For now, I fixed the the nitpick. For the rest, just like E-Mail validation, we have to make the decision to split work on this into a separate PR or work on it in this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, removed the limitation on Also added a warning to the docs that this should be used carefully as it might lead to fragile terraform projects: |
||
return resource.NonRetryableError(fmt.Errorf("Certificate has type %s, only AMAZON_ISSUED is supported at the moment", *resp.Certificate.Type)) | ||
} | ||
|
||
if err := d.Set("domain_name", resp.Certificate.DomainName); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: for anything that's a simple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, I guess that was me getting paranoid after I missed some errors on other |
||
return resource.NonRetryableError(err) | ||
} | ||
if err := d.Set("certificate_arn", resp.Certificate.CertificateArn); err != nil { | ||
return resource.NonRetryableError(err) | ||
} | ||
if err := d.Set("validation_method", resp.Certificate.DomainValidationOptions[0].ValidationMethod); err != nil { | ||
return resource.NonRetryableError(err) | ||
} | ||
if err := d.Set("subject_alternative_names", cleanUpSubjectAlternativeNames(resp.Certificate)); err != nil { | ||
return resource.NonRetryableError(err) | ||
} | ||
|
||
domainValidationOptions, err := convertDomainValidationOptions(resp.Certificate.DomainValidationOptions) | ||
|
||
if err != nil { | ||
return resource.RetryableError(err) | ||
} | ||
|
||
if err := d.Set("domain_validation_options", domainValidationOptions); err != nil { | ||
return resource.NonRetryableError(err) | ||
} | ||
|
||
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 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 convertDomainValidationOptions(validations []*acm.DomainValidation) ([]map[string]interface{}, error) { | ||
var result []map[string]interface{} | ||
|
||
for _, o := range validations { | ||
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, | ||
} | ||
result = append(result, validationOption) | ||
} else { | ||
log.Printf("[DEBUG] No resource record found in validation options, need to retry: %#v", o) | ||
return nil, fmt.Errorf("No resource record found in DNS DomainValidationOptions: %v", o) | ||
} | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func resourceAwsAcmCertificateDelete(d *schema.ResourceData, meta interface{}) error { | ||
acmconn := meta.(*AWSClient).acmconn | ||
|
||
if err := resourceAwsAcmCertificateRead(d, meta); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a little heavier handed than it needs to be. Can we get away with just catching something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I probably stole that from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We actually do that in a bunch of resources as well. Probably good to add into this one for good measure too 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh and by that I mean in both read and delete. Some folks when they get stuck in a really bad configuration for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, makes sense. Added the check to read in both resources as well. |
||
return err | ||
} | ||
if d.Id() == "" { | ||
// This might happen from the read | ||
return nil | ||
} | ||
|
||
params := &acm.DeleteCertificateInput{ | ||
CertificateArn: aws.String(d.Id()), | ||
} | ||
|
||
_, err := acmconn.DeleteCertificate(params) | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error deleting certificate: %s", err) | ||
} | ||
|
||
d.SetId("") | ||
return nil | ||
} |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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!