terraform-provider-greenhost/digitalocean/resource_digitalocean_certi...

252 lines
6.5 KiB
Go
Raw Normal View History

package digitalocean
import (
"context"
"fmt"
"log"
"time"
"github.com/digitalocean/godo"
Upgrade to terraform-plugin-sdk (#321) * Upgrade to terraform-plugin-sdk After following the [guide](https://www.terraform.io/docs/extend/plugin-sdk.html#using-tf-sdk-migrator) Check ``` ▶ GO111MODULE=on tf-sdk-migrator check Checking Go runtime version ... Go version 1.12.10: OK. Checking whether provider uses Go modules... Go modules in use: OK. Checking version of github.com/hashicorp/terraform-plugin-sdk to determine if provider was already migrated... Checking version of github.com/hashicorp/terraform used in provider... Terraform version 0.12.8: OK. Checking whether provider uses deprecated SDK packages or identifiers... No imports of deprecated SDK packages or identifiers: OK. All constraints satisfied. Provider can be migrated to the new SDK. ``` Migrate ``` ▶ GO111MODULE=on tf-sdk-migrator migrate Checking Go runtime version ... Go version 1.12.10: OK. Checking whether provider uses Go modules... Go modules in use: OK. Checking version of github.com/hashicorp/terraform-plugin-sdk to determine if provider was already migrated... Checking version of github.com/hashicorp/terraform used in provider... Terraform version 0.12.8: OK. Checking whether provider uses deprecated SDK packages or identifiers... No imports of deprecated SDK packages or identifiers: OK. All constraints satisfied. Provider can be migrated to the new SDK. Rewriting provider go.mod file... Rewriting SDK package imports... Running `go mod tidy`... Success! Provider is migrated to github.com/hashicorp/terraform-plugin-sdk v1.1.0. It looks like this provider vendors dependencies. Don't forget to run `go mod vendor`. Make sure to review all changes and run all tests. ``` * Fix build under go 1.13.x.
2019-10-22 21:44:03 +00:00
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)
func resourceDigitalOceanCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanCertificateCreate,
Read: resourceDigitalOceanCertificateRead,
Delete: resourceDigitalOceanCertificateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},
"private_key": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},
"leaf_certificate": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},
"certificate_chain": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},
"domains": {
Type: schema.TypeSet,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.NoZeroValues,
},
Optional: true,
ForceNew: true,
ConflictsWith: []string{"private_key", "leaf_certificate", "certificate_chain"},
2018-10-04 18:26:28 +00:00
// The domains attribute is computed for custom certs and should be ignored in diffs.
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return d.Get("type") == "custom"
},
},
"type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "custom",
ValidateFunc: validation.StringInSlice([]string{
"custom",
"lets_encrypt",
}, false),
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"not_after": {
Type: schema.TypeString,
Computed: true,
},
"sha1_fingerprint": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func buildCertificateRequest(d *schema.ResourceData) (*godo.CertificateRequest, error) {
req := &godo.CertificateRequest{
Name: d.Get("name").(string),
Type: d.Get("type").(string),
}
if v, ok := d.GetOk("private_key"); ok {
req.PrivateKey = v.(string)
}
if v, ok := d.GetOk("leaf_certificate"); ok {
req.LeafCertificate = v.(string)
}
if v, ok := d.GetOk("certificate_chain"); ok {
req.CertificateChain = v.(string)
}
if v, ok := d.GetOk("domains"); ok {
req.DNSNames = expandDigitalOceanCertificateDomains(v.(*schema.Set).List())
}
return req, nil
}
func resourceDigitalOceanCertificateCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
Certificates: Move attribute validation from CustomizeDiff to Create (Fixes: #163). When CustomizeDiff is run for a new resource, it does not have access to the current state. In the digitalocean_certificate resource, we are using a CustomizeDiff function to validate that certain attributes are used together depending on the certificate type. When the value for one of those attributes is interpolated, as in the case of generating a cert using the acme provider, the validation fails since it depends on reading the value from state. As state is not present, the value is interpreted as empty. From the CustomizeDiff doc string: > // The phases Terraform runs this in, and the state available via functions > // like Get and GetChange, are as follows: > // > // * New resource: One run with no state https://godoc.org/github.com/hashicorp/terraform/helper/schema#Resource This PR moves the validation from the CustomizeDiff function into the Create function as well as adding acceptance tests: ``` $ make testacc TESTARGS='-run=TestAccDigitalOceanCertificate_ExpectedErrors' ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -run=TestAccDigitalOceanCertificate_ExpectedE$ ? github.com/terraform-providers/terraform-provider-digitalocean [no test files] === RUN TestAccDigitalOceanCertificate_ExpectedErrors --- PASS: TestAccDigitalOceanCertificate_ExpectedErrors (0.10s) PASS ok github.com/terraform-providers/terraform-provider-digitalocean/digitalocean (cached) ```
2019-03-28 18:13:22 +00:00
certificateType := d.Get("type").(string)
if certificateType == "custom" {
if _, ok := d.GetOk("private_key"); !ok {
return fmt.Errorf("`private_key` is required for when type is `custom` or empty")
}
if _, ok := d.GetOk("leaf_certificate"); !ok {
return fmt.Errorf("`leaf_certificate` is required for when type is `custom` or empty")
}
} else if certificateType == "lets_encrypt" {
if _, ok := d.GetOk("domains"); !ok {
return fmt.Errorf("`domains` is required for when type is `lets_encrypt`")
}
}
log.Printf("[INFO] Create a Certificate Request")
certReq, err := buildCertificateRequest(d)
if err != nil {
return err
}
log.Printf("[DEBUG] Certificate Create: %#v", certReq)
cert, _, err := client.Certificates.Create(context.Background(), certReq)
if err != nil {
return fmt.Errorf("Error creating Certificate: %s", err)
}
d.SetId(cert.ID)
2018-09-04 19:00:54 +00:00
log.Printf("[INFO] Waiting for certificate (%s) to have state 'verified'", cert.ID)
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"verified"},
Refresh: newCertificateStateRefreshFunc(d, meta),
Timeout: d.Timeout(schema.TimeoutCreate),
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for certificate (%s) to become active: %s", d.Get("name"), err)
}
return resourceDigitalOceanCertificateRead(d, meta)
}
func resourceDigitalOceanCertificateRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
log.Printf("[INFO] Reading the details of the Certificate %s", d.Id())
cert, resp, err := client.Certificates.Get(context.Background(), d.Id())
if err != nil {
// check if the certificate no longer exists.
if resp != nil && resp.StatusCode == 404 {
log.Printf("[WARN] DigitalOcean Certificate (%s) not found", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error retrieving Certificate: %s", err)
}
d.Set("name", cert.Name)
d.Set("type", cert.Type)
d.Set("state", cert.State)
d.Set("not_after", cert.NotAfter)
d.Set("sha1_fingerprint", cert.SHA1Fingerprint)
if err := d.Set("domains", flattenDigitalOceanCertificateDomains(cert.DNSNames)); err != nil {
return fmt.Errorf("Error setting `domains`: %+v", err)
}
return nil
}
func resourceDigitalOceanCertificateDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*CombinedConfig).godoClient()
log.Printf("[INFO] Deleting Certificate: %s", d.Id())
_, err := client.Certificates.Delete(context.Background(), d.Id())
if err != nil {
return fmt.Errorf("Error deleting Certificate: %s", err)
}
return nil
}
func expandDigitalOceanCertificateDomains(domains []interface{}) []string {
expandedDomains := make([]string, len(domains))
for i, v := range domains {
expandedDomains[i] = v.(string)
}
return expandedDomains
}
func flattenDigitalOceanCertificateDomains(domains []string) *schema.Set {
if domains == nil {
return nil
}
flattenedDomains := schema.NewSet(schema.HashString, []interface{}{})
for _, v := range domains {
if v != "" {
flattenedDomains.Add(v)
}
}
return flattenedDomains
}
func newCertificateStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
client := meta.(*CombinedConfig).godoClient()
return func() (interface{}, string, error) {
// Retrieve the certificate properties
cert, _, err := client.Certificates.Get(context.Background(), d.Id())
if err != nil {
return nil, "", fmt.Errorf("Error retrieving certifica: %s", err)
}
return cert, cert.State, nil
}
}