Skip to content

Commit

Permalink
Merge pull request #9911 from terraform-providers/f/key-vault-soft-de…
Browse files Browse the repository at this point in the history
…lete

key-vault/nested items: support for purging deleted items
  • Loading branch information
tombuildsstuff authored Dec 17, 2020
2 parents 449bac7 + 8bcf259 commit c17449f
Show file tree
Hide file tree
Showing 17 changed files with 733 additions and 1,162 deletions.
144 changes: 144 additions & 0 deletions azurerm/internal/services/keyvault/internal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package keyvault

import (
"context"
"fmt"
"log"
"net/http"
"time"

"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

type deleteAndPurgeNestedItem interface {
DeleteNestedItem(ctx context.Context) (autorest.Response, error)
NestedItemHasBeenDeleted(ctx context.Context) (autorest.Response, error)

PurgeNestedItem(ctx context.Context) (autorest.Response, error)
NestedItemHasBeenPurged(ctx context.Context) (autorest.Response, error)
}

func deleteAndOptionallyPurge(ctx context.Context, description string, shouldPurge bool, helper deleteAndPurgeNestedItem) error {
timeout, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("context is missing a timeout")
}

log.Printf("[DEBUG] Deleting %s..", description)
if resp, err := helper.DeleteNestedItem(ctx); err != nil {
if utils.ResponseWasNotFound(resp) {
return nil
}

return fmt.Errorf("deleting %s: %+v", description, err)
}
log.Printf("[DEBUG] Waiting for %s to finish deleting..", description)
stateConf := &resource.StateChangeConf{
Pending: []string{"InProgress"},
Target: []string{"NotFound"},
Refresh: func() (interface{}, string, error) {
item, err := helper.NestedItemHasBeenDeleted(ctx)
if err != nil {
if utils.ResponseWasNotFound(item) {
return item, "NotFound", nil
}

return nil, "Error", err
}

return item, "InProgress", nil
},
ContinuousTargetOccurence: 3,
PollInterval: 5 * time.Second,
Timeout: time.Until(timeout),
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for %s to be deleted: %+v", description, err)
}
log.Printf("[DEBUG] Deleted %s.", description)

if !shouldPurge {
log.Printf("[DEBUG] Skipping purging of %s as opted-out..", description)
return nil
}

log.Printf("[DEBUG] Purging %s..", description)
if _, err := helper.PurgeNestedItem(ctx); err != nil {
return fmt.Errorf("purging %s: %+v", description, err)
}

log.Printf("[DEBUG] Waiting for %s to finish purging..", description)
stateConf = &resource.StateChangeConf{
Pending: []string{"InProgress"},
Target: []string{"NotFound"},
Refresh: func() (interface{}, string, error) {
item, err := helper.NestedItemHasBeenPurged(ctx)
if err != nil {
if utils.ResponseWasNotFound(item) {
return item, "NotFound", nil
}

return nil, "Error", err
}

return item, "InProgress", nil
},
ContinuousTargetOccurence: 3,
PollInterval: 5 * time.Second,
Timeout: time.Until(timeout),
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("waiting for %s to finish purging: %+v", description, err)
}
log.Printf("[DEBUG] Purged %s.", description)

return nil
}

func keyVaultChildItemRefreshFunc(secretUri string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] Checking to see if KeyVault Secret %q is available..", secretUri)

PTransport := &http.Transport{Proxy: http.ProxyFromEnvironment}

client := &http.Client{
Transport: PTransport,
}

conn, err := client.Get(secretUri)
if err != nil {
log.Printf("[DEBUG] Didn't find KeyVault secret at %q", secretUri)
return nil, "pending", fmt.Errorf("Error checking secret at %q: %s", secretUri, err)
}

defer conn.Body.Close()

log.Printf("[DEBUG] Found KeyVault Secret %q", secretUri)
return "available", "available", nil
}
}

func nestedItemResourceImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
client := meta.(*clients.Client).KeyVault.VaultsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := azure.ParseKeyVaultChildID(d.Id())
if err != nil {
return []*schema.ResourceData{d}, fmt.Errorf("parsing ID %q for Key Vault Child import: %v", d.Id(), err)
}

keyVaultId, err := azure.GetKeyVaultIDFromBaseUrl(ctx, client, id.KeyVaultBaseUrl)
if err != nil {
return []*schema.ResourceData{d}, fmt.Errorf("retrieving the Resource ID the Key Vault at URL %q: %s", id.KeyVaultBaseUrl, err)
}
d.Set("key_vault_id", keyVaultId)

return []*schema.ResourceData{d}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func resourceArmKeyVaultCertificateIssuer() *schema.Resource {
Read: resourceArmKeyVaultCertificateIssuerRead,
Delete: resourceArmKeyVaultCertificateIssuerDelete,
Importer: &schema.ResourceImporter{
State: resourceArmKeyVaultChildResourceImporter,
State: nestedItemResourceImporter,
},

Timeouts: &schema.ResourceTimeout{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
Expand All @@ -24,27 +25,6 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

// todo refactor and find a home for this wayward func
func resourceArmKeyVaultChildResourceImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
client := meta.(*clients.Client).KeyVault.VaultsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := azure.ParseKeyVaultChildID(d.Id())
if err != nil {
return []*schema.ResourceData{d}, fmt.Errorf("Error Unable to parse ID (%s) for Key Vault Child import: %v", d.Id(), err)
}

kvid, err := azure.GetKeyVaultIDFromBaseUrl(ctx, client, id.KeyVaultBaseUrl)
if err != nil {
return []*schema.ResourceData{d}, fmt.Errorf("Error retrieving the Resource ID the Key Vault at URL %q: %s", id.KeyVaultBaseUrl, err)
}

d.Set("key_vault_id", kvid)

return []*schema.ResourceData{d}, nil
}

func resourceArmKeyVaultCertificate() *schema.Resource {
return &schema.Resource{
// TODO: support Updating once we have more information about what can be updated
Expand All @@ -53,7 +33,7 @@ func resourceArmKeyVaultCertificate() *schema.Resource {
Delete: resourceArmKeyVaultCertificateDelete,

Importer: &schema.ResourceImporter{
State: resourceArmKeyVaultChildResourceImporter,
State: nestedItemResourceImporter,
},

Timeouts: &schema.ResourceTimeout{
Expand Down Expand Up @@ -235,6 +215,7 @@ func resourceArmKeyVaultCertificate() *schema.Resource {
"key_usage": {
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
Expand Down Expand Up @@ -602,18 +583,47 @@ func resourceArmKeyVaultCertificateDelete(d *schema.ResourceData, meta interface
return nil
}

resp, err := client.DeleteCertificate(ctx, id.KeyVaultBaseUrl, id.Name)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return nil
}

return fmt.Errorf("Error deleting Certificate %q from Key Vault: %+v", id.Name, err)
shouldPurge := meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy
description := fmt.Sprintf("Certificate %q (Key Vault %q)", id.Name, id.KeyVaultBaseUrl)
deleter := deleteAndPurgeCertificate{
client: client,
keyVaultUri: id.KeyVaultBaseUrl,
name: id.Name,
}
if err := deleteAndOptionallyPurge(ctx, description, shouldPurge, deleter); err != nil {
return err
}

return nil
}

var _ deleteAndPurgeNestedItem = deleteAndPurgeCertificate{}

type deleteAndPurgeCertificate struct {
client *keyvault.BaseClient
keyVaultUri string
name string
}

func (d deleteAndPurgeCertificate) DeleteNestedItem(ctx context.Context) (autorest.Response, error) {
resp, err := d.client.DeleteCertificate(ctx, d.keyVaultUri, d.name)
return resp.Response, err
}

func (d deleteAndPurgeCertificate) NestedItemHasBeenDeleted(ctx context.Context) (autorest.Response, error) {
resp, err := d.client.GetCertificate(ctx, d.keyVaultUri, d.name, "")
return resp.Response, err
}

func (d deleteAndPurgeCertificate) PurgeNestedItem(ctx context.Context) (autorest.Response, error) {
return d.client.PurgeDeletedCertificate(ctx, d.keyVaultUri, d.name)
}

func (d deleteAndPurgeCertificate) NestedItemHasBeenPurged(ctx context.Context) (autorest.Response, error) {
resp, err := d.client.GetDeletedCertificate(ctx, d.keyVaultUri, d.name)
return resp.Response, err
}

func expandKeyVaultCertificatePolicy(d *schema.ResourceData) keyvault.CertificatePolicy {
policies := d.Get("certificate_policy").([]interface{})
policyRaw := policies[0].(map[string]interface{})
Expand Down
Loading

0 comments on commit c17449f

Please sign in to comment.