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

[WIP] New Resource: aws_acm_certificate (+ changes to data source to wait for certificate issuing) #2801

Closed
65 changes: 58 additions & 7 deletions aws/data_source_aws_acm_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/acm"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

Expand All @@ -17,11 +18,13 @@ func dataSourceAwsAcmCertificate() *schema.Resource {
Schema: map[string]*schema.Schema{
"domain": {
Type: schema.TypeString,
Required: true,
Optional: true,
Computed: true,
},
"arn": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"statuses": {
Type: schema.TypeList,
Expand All @@ -33,15 +36,47 @@ func dataSourceAwsAcmCertificate() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"wait_until_present": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"wait_until_present_timeout": {
Type: schema.TypeString,
Optional: true,
Default: "45m",
},
},
}
}

func dataSourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) error {
if d.Get("wait_until_present").(bool) {

timeout, err := time.ParseDuration(d.Get("wait_until_present_timeout").(string))
if err != nil {
return err
}
return resource.Retry(timeout, func() *resource.RetryError {
return dataSourceAwsAcmGetCertificate(d, meta)
})
} else {
return dataSourceAwsAcmGetCertificate(d, meta).Err
}
}

func dataSourceAwsAcmGetCertificate(d *schema.ResourceData, meta interface{}) *resource.RetryError {
conn := meta.(*AWSClient).acmconn

params := &acm.ListCertificatesInput{}
target := d.Get("domain")

targetDomain, _ := d.GetOk("domain")
targetArn, _ := d.GetOk("arn")

if targetArn == nil && targetDomain == nil {
return resource.NonRetryableError(fmt.Errorf("Need to specify either domain or arn"))
}

statuses, ok := d.GetOk("statuses")
if ok {
statusStrings := statuses.([]interface{})
Expand All @@ -51,54 +86,70 @@ func dataSourceAwsAcmCertificateRead(d *schema.ResourceData, meta interface{}) e
}

var arns []string
var domains []string

log.Printf("[DEBUG] Reading ACM Certificate: %s", params)
err := conn.ListCertificatesPages(params, func(page *acm.ListCertificatesOutput, lastPage bool) bool {
for _, cert := range page.CertificateSummaryList {
if *cert.DomainName == target {
if targetDomain != nil && *cert.DomainName == targetDomain {
arns = append(arns, *cert.CertificateArn)
domains = append(domains, *cert.DomainName)
}
if targetArn != nil && *cert.CertificateArn == targetArn {
arns = append(arns, *cert.CertificateArn)
domains = append(domains, *cert.DomainName)
}
}

return true
})
if err != nil {
return errwrap.Wrapf("Error describing certificates: {{err}}", err)
return resource.NonRetryableError(errwrap.Wrapf("Error describing certificates: {{err}}", err))
}

// filter based on certificate type (imported or aws-issued)
types, ok := d.GetOk("types")
if ok {
typesStrings := expandStringList(types.([]interface{}))
var matchedArns []string
var matchedDomains []string
for _, arn := range arns {
params := &acm.DescribeCertificateInput{}
params.CertificateArn = &arn

description, err := conn.DescribeCertificate(params)
if err != nil {
return errwrap.Wrapf("Error describing certificates: {{err}}", err)
return resource.NonRetryableError(errwrap.Wrapf("Error describing certificates: {{err}}", err))
}

for _, certType := range typesStrings {
if *description.Certificate.Type == *certType {
matchedArns = append(matchedArns, arn)
matchedDomains = append(domains, *description.Certificate.DomainName)
break
}
}
}

arns = matchedArns
domains = matchedDomains
}

targetValue := targetArn
if targetValue == nil {
targetValue = targetDomain
}

if len(arns) == 0 {
return fmt.Errorf("No certificate for domain %q found in this region.", target)
return resource.RetryableError(fmt.Errorf("No certificate for domain %q found in this region.", targetValue))
}
if len(arns) > 1 {
return fmt.Errorf("Multiple certificates for domain %q found in this region.", target)
return resource.NonRetryableError(fmt.Errorf("Multiple certificates for domain %q found in this region.", targetValue))
}

d.SetId(time.Now().UTC().String())
d.Set("arn", arns[0])
d.Set("domain", domains[0])

return nil
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"aws_acm_certificate": resourceAwsAcmCertificate(),
"aws_ami": resourceAwsAmi(),
"aws_ami_copy": resourceAwsAmiCopy(),
"aws_ami_from_instance": resourceAwsAmiFromInstance(),
Expand Down
190 changes: 190 additions & 0 deletions aws/resource_aws_acm_certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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,
Delete: resourceAwsAcmCertificateDelete,

Schema: map[string]*schema.Schema{
"domain_name": &schema.Schema{
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,
},
"certificate_arn": &schema.Schema{
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{
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,
},
},
},
},
},
}
}

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"),
}

// TODO: check that validation method is DNS, nothing else supported at the moment

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)
d.Set("certificate_arn", *resp.CertificateArn)

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 err := d.Set("domain_name", resp.Certificate.DomainName); 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)
}

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) {
result := make([]map[string]interface{}, 0, len(validations))

for _, o := range validations {
validationOption := make(map[string]interface{})
validationOption["domain_name"] = *o.DomainName
if o.ResourceRecord != nil {
validationOption["resource_record_name"] = *o.ResourceRecord.Name
validationOption["resource_record_type"] = *o.ResourceRecord.Type
validationOption["resource_record_value"] = *o.ResourceRecord.Value
} 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)
}

result = append(result, validationOption)
}

return result, nil
}

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

if err := resourceAwsAcmCertificateRead(d, meta); err != nil {
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
}
Loading