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

key-vault/nested items: support for purging deleted items #9911

Merged
merged 18 commits into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3cb95f1
r/keyvault: fixing a bug where application id couldn't be an empty st…
tombuildsstuff Dec 16, 2020
5d289a8
keyvault/nested-resources: purging during deletion
tombuildsstuff Dec 17, 2020
3fa852e
keyvault/nested-items: reusing the `purge_soft_delete_on_destroy` fea…
tombuildsstuff Dec 17, 2020
b0ab568
key-vault/nested-item: moving the wayward func
tombuildsstuff Dec 17, 2020
b9c5ca4
keyvault/nested-item: updating the docs
tombuildsstuff Dec 17, 2020
7ecfd90
website: updating the features block
tombuildsstuff Dec 17, 2020
b124d42
r/key_vault_certificate: adding a ForceNew
tombuildsstuff Dec 17, 2020
14744b0
keyvault: quoting the ID to handle escape on shells
tombuildsstuff Dec 17, 2020
6f6ca1f
r/key_vault_certificate: refactoring the tests to use a template wher…
tombuildsstuff Dec 17, 2020
f36f5ad
r/key_vault_certificate: adding the purge permission to the tests
tombuildsstuff Dec 17, 2020
0ef6fcf
r/key_vault_certificate: and to the docs
tombuildsstuff Dec 17, 2020
ca034e3
r/key_vault_certificate: enabling soft-delete for the tests by default
tombuildsstuff Dec 17, 2020
31df6de
r/key_vault_certificate: adding soft delete to the example
tombuildsstuff Dec 17, 2020
cd42a1d
keyvault/key & secret: updating the examples
tombuildsstuff Dec 17, 2020
22e62fe
r/key_vault_key & secret: updating the tests to use templates & enabl…
tombuildsstuff Dec 17, 2020
752d39a
r/key_vault: enabling soft delete by default
tombuildsstuff Dec 17, 2020
9406666
r/key_vault_certificate: adding import/recover permissions for the tests
tombuildsstuff Dec 17, 2020
8bcf259
r/key_vault_secret: adding purge protection
tombuildsstuff Dec 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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