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

335 lines
9.3 KiB
Go
Raw Normal View History

package digitalocean
import (
"context"
"fmt"
"log"
"time"
"github.com/digitalocean/godo"
upgrade provider to use terraform-plugin-sdk v2 (#492) * upgrade terraform-plugin-sdk and `go mod vendor` * Update digitalocean/datasource_digitalocean_image_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_kubernetes_cluster_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_vpc_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_vpc_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * go fmt * fix droplet_id to be of the right type * fix digitalocean_project resource * fix creation order in digitalocean_certificate test * fix digitalocean_container_registry data source tes * Port new changes to v2. * Port all tests to resource.ParallelTest * Fix KubernetesProviderInteroperability test. * Fix TestAccDigitalOceanKubernetesCluster_UpgradeVersion * Fix firewall panic s/create_at/created_at/ * Fix TestAccDigitalOceanDroplet_Basic: Droplets now have private networking by default. * Fix TestAccDataSourceDigitalOceanDomain_Basic * Fix TestAccDataSourceDigitalOceanDropletSnapshot tests. * Fix TestAccDataSourceDigitalOceanSSHKey_Basic * Fix TestAccDataSourceDigitalOceanVolumeSnapshot tests. * Fix TestAccDataSourceDigitalOceanVolume tests. * Fix TestAccDataSourceDigitalOceanRecord_Basic * Fix TestAccDataSourceDigitalOceanProject_NonDefaultProject * Fix TestAccDigitalOceanImage_PublicSlug * Fix TestAccDataSourceDigitalOceanImages_Basic via bug in imageSchema() * go mod tidy * Fix TestAccDataSourceDigitalOceanDroplet tests. * Fix TestAccDataSourceDigitalOceanVPC_ByName * Fix TestAccDataSourceDigitalOceanTag_Basic * Fix TestAccDataSourceDigitalOceanTags_Basic * Ensure versions are set in DBaaS tests. * Fix TestAccDataSourceDigitalOceanApp_Basic * Fix non-set related issues with TestAccDataSourceDigitalOceanLoadBalancer tests. * Fix TestAccDataSourceDigitalOceanKubernetesCluster_Basic * Remove testAccDigitalOceanKubernetesConfigWithEmptyNodePool: Empty node pools are no longer supported. * Fix TestAccDigitalOceanProject_WithManyResources. * Fix TestAccDigitalOceanProject_UpdateFromDropletToSpacesResource * vendor set helpers from AWS provider * Fix TestAccDigitalOceanFloatingIP_Droplet. * Fix CDN panic. * fix TestAccDigitalOceanSpacesBucket_LifecycleBasic using setutil helpers * vendor set helpers from AWS provider * fix TestAccDigitalOceanSpacesBucket_LifecycleBasic using setutil helpers * Fix load balancer tests using setutil helpers. * Fix K8s tests using setutil helpers. * Fix TestAccDigitalOceanApp_Envs using setutil helpers. * Fix TestAccDigitalOceanSpacesBucket_LifecycleExpireMarkerOnly using setutil helpers. * Fix TestAccDigitalOceanFloatingIPAssignment_createBeforeDestroy * fix remaining TypeSet tests using setutil * Registry test can not run in parallel. One per account. * Fix TestAccDigitalOceanProject_UpdateWithDropletResource * Fix replica tests. * go mod tidy Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> Co-authored-by: Andrew Starr-Bochicchio <a.starr.b@gmail.com>
2020-10-16 19:50:20 +00:00
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func resourceDigitalOceanCertificate() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanCertificateCreate,
Read: resourceDigitalOceanCertificateRead,
Delete: resourceDigitalOceanCertificateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: resourceDigitalOceanCertificateV1(),
SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceDigitalOceanCertificateV0().CoreConfigSchema().ImpliedType(),
Upgrade: migrateCertificateStateV0toV1,
Version: 0,
},
},
}
}
func resourceDigitalOceanCertificateV1() map[string]*schema.Schema {
certificateV1Schema := map[string]*schema.Schema{
// Note that this UUID will change on auto-renewal of a
// lets_encrypt certificate.
"uuid": {
Type: schema.TypeString,
Computed: true,
},
}
for k, v := range resourceDigitalOceanCertificateV0().Schema {
certificateV1Schema[k] = v
}
return certificateV1Schema
}
func resourceDigitalOceanCertificateV0() *schema.Resource {
return &schema.Resource{
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,
StateFunc: HashStringStateFunc(),
// In order to support older statefiles with fully saved private_key
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new != "" && old == d.Get("private_key")
},
},
"leaf_certificate": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
StateFunc: HashStringStateFunc(),
// In order to support older statefiles with fully saved leaf_certificate
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new != "" && old == d.Get("leaf_certificate")
},
},
"certificate_chain": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
StateFunc: HashStringStateFunc(),
// In order to support older statefiles with fully saved certificate_chain
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new != "" && old == d.Get("certificate_chain")
},
},
"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,
},
},
}
}
upgrade provider to use terraform-plugin-sdk v2 (#492) * upgrade terraform-plugin-sdk and `go mod vendor` * Update digitalocean/datasource_digitalocean_image_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_kubernetes_cluster_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_vpc_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * Update digitalocean/datasource_digitalocean_vpc_test.go Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> * go fmt * fix droplet_id to be of the right type * fix digitalocean_project resource * fix creation order in digitalocean_certificate test * fix digitalocean_container_registry data source tes * Port new changes to v2. * Port all tests to resource.ParallelTest * Fix KubernetesProviderInteroperability test. * Fix TestAccDigitalOceanKubernetesCluster_UpgradeVersion * Fix firewall panic s/create_at/created_at/ * Fix TestAccDigitalOceanDroplet_Basic: Droplets now have private networking by default. * Fix TestAccDataSourceDigitalOceanDomain_Basic * Fix TestAccDataSourceDigitalOceanDropletSnapshot tests. * Fix TestAccDataSourceDigitalOceanSSHKey_Basic * Fix TestAccDataSourceDigitalOceanVolumeSnapshot tests. * Fix TestAccDataSourceDigitalOceanVolume tests. * Fix TestAccDataSourceDigitalOceanRecord_Basic * Fix TestAccDataSourceDigitalOceanProject_NonDefaultProject * Fix TestAccDigitalOceanImage_PublicSlug * Fix TestAccDataSourceDigitalOceanImages_Basic via bug in imageSchema() * go mod tidy * Fix TestAccDataSourceDigitalOceanDroplet tests. * Fix TestAccDataSourceDigitalOceanVPC_ByName * Fix TestAccDataSourceDigitalOceanTag_Basic * Fix TestAccDataSourceDigitalOceanTags_Basic * Ensure versions are set in DBaaS tests. * Fix TestAccDataSourceDigitalOceanApp_Basic * Fix non-set related issues with TestAccDataSourceDigitalOceanLoadBalancer tests. * Fix TestAccDataSourceDigitalOceanKubernetesCluster_Basic * Remove testAccDigitalOceanKubernetesConfigWithEmptyNodePool: Empty node pools are no longer supported. * Fix TestAccDigitalOceanProject_WithManyResources. * Fix TestAccDigitalOceanProject_UpdateFromDropletToSpacesResource * vendor set helpers from AWS provider * Fix TestAccDigitalOceanFloatingIP_Droplet. * Fix CDN panic. * fix TestAccDigitalOceanSpacesBucket_LifecycleBasic using setutil helpers * vendor set helpers from AWS provider * fix TestAccDigitalOceanSpacesBucket_LifecycleBasic using setutil helpers * Fix load balancer tests using setutil helpers. * Fix K8s tests using setutil helpers. * Fix TestAccDigitalOceanApp_Envs using setutil helpers. * Fix TestAccDigitalOceanSpacesBucket_LifecycleExpireMarkerOnly using setutil helpers. * Fix TestAccDigitalOceanFloatingIPAssignment_createBeforeDestroy * fix remaining TypeSet tests using setutil * Registry test can not run in parallel. One per account. * Fix TestAccDigitalOceanProject_UpdateWithDropletResource * Fix replica tests. * go mod tidy Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com> Co-authored-by: Andrew Starr-Bochicchio <a.starr.b@gmail.com>
2020-10-16 19:50:20 +00:00
func migrateCertificateStateV0toV1(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
if len(rawState) == 0 {
log.Println("[DEBUG] Empty state; nothing to migrate.")
return rawState, nil
}
log.Println("[DEBUG] Migrating certificate schema from v0 to v1.")
// When the certificate type is lets_encrypt, the certificate
// ID will change when it's renewed, so we have to rely on the
// certificate name as the primary identifier instead.
rawState["uuid"] = rawState["id"]
rawState["id"] = rawState["name"]
return rawState, nil
}
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)
}
// When the certificate type is lets_encrypt, the certificate
// ID will change when it's renewed, so we have to rely on the
// certificate name as the primary identifier instead.
d.SetId(cert.Name)
// We include the UUID as another computed field for use in the
// short-term refresh function that waits for it to be ready.
err = d.Set("uuid", cert.ID)
log.Printf("[INFO] Waiting for certificate (%s) to have state 'verified'", cert.Name)
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()
// When the certificate type is lets_encrypt, the certificate
// ID will change when it's renewed, so we have to rely on the
// certificate name as the primary identifier instead.
log.Printf("[INFO] Reading the details of the Certificate %s", d.Id())
cert, err := findCertificateByName(client, d.Id())
if err != nil {
return fmt.Errorf("Error retrieving Certificate: %s", err)
}
// check if the certificate no longer exists.
if cert == nil {
log.Printf("[WARN] DigitalOcean Certificate (%s) not found", d.Id())
d.SetId("")
return nil
}
d.Set("name", cert.Name)
d.Set("uuid", cert.ID)
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())
cert, err := findCertificateByName(client, d.Id())
if err != nil {
return fmt.Errorf("Error retrieving Certificate: %s", err)
}
if cert == nil {
return nil
}
_, err = client.Certificates.Delete(context.Background(), cert.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
uuid := d.Get("uuid").(string)
cert, _, err := client.Certificates.Get(context.Background(), uuid)
if err != nil {
return nil, "", fmt.Errorf("Error retrieving certificate: %s", err)
}
return cert, cert.State, nil
}
}