From 39fb80f2e4e1d12a714e61dadc8b1523337f5962 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 8 Jan 2020 12:33:01 -0800 Subject: [PATCH 01/33] Key Vault: support for soft delete --- azurerm/helpers/validate/bool.go | 23 ++++ azurerm/helpers/validate/bool_test.go | 37 ++++++ .../keyvault/data_source_key_vault.go | 12 ++ .../keyvault/resource_arm_key_vault.go | 49 ++++++++ .../tests/data_source_key_vault_test.go | 32 ++++++ .../tests/resource_arm_key_vault_test.go | 106 ++++++++++++++++++ website/docs/d/key_vault.html.markdown | 4 + website/docs/r/key_vault.html.markdown | 6 + 8 files changed, 269 insertions(+) create mode 100644 azurerm/helpers/validate/bool.go create mode 100644 azurerm/helpers/validate/bool_test.go diff --git a/azurerm/helpers/validate/bool.go b/azurerm/helpers/validate/bool.go new file mode 100644 index 000000000000..3c7901b3636c --- /dev/null +++ b/azurerm/helpers/validate/bool.go @@ -0,0 +1,23 @@ +package validate + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func BoolIsTrue() schema.SchemaValidateFunc { + return func(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(bool) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be bool", k)) + return + } + + if !v { + errors = append(errors, fmt.Errorf("%q can only be set to true, if not required remove key", k)) + return + } + return + } +} diff --git a/azurerm/helpers/validate/bool_test.go b/azurerm/helpers/validate/bool_test.go new file mode 100644 index 000000000000..d9ba6b9627a9 --- /dev/null +++ b/azurerm/helpers/validate/bool_test.go @@ -0,0 +1,37 @@ +package validate + +import "testing" + +func TestBoolIsTrue(t *testing.T) { + + testCases := []struct { + Value bool + ShouldHaveError bool + }{ + { + Value: true, + ShouldHaveError: false, + }, { + Value: false, + ShouldHaveError: true, + }, + } + + t.Run("TestBoolIsTrue", func(t *testing.T) { + for _, value := range testCases { + _, errors := BoolIsTrue()(value.Value, "dummy") + hasErrors := len(errors) > 0 + + if value.ShouldHaveError && !hasErrors { + t.Fatalf("Expected an error but didn't get one for %t", value.Value) + return + } + + if !value.ShouldHaveError && hasErrors { + t.Fatalf("Expected %t to return no errors, but got some %+v", value.Value, errors) + return + } + + } + }) +} \ No newline at end of file diff --git a/azurerm/internal/services/keyvault/data_source_key_vault.go b/azurerm/internal/services/keyvault/data_source_key_vault.go index ef5bf1b85f54..6117855e4e12 100644 --- a/azurerm/internal/services/keyvault/data_source_key_vault.go +++ b/azurerm/internal/services/keyvault/data_source_key_vault.go @@ -156,6 +156,16 @@ func dataSourceArmKeyVault() *schema.Resource { }, }, + "enabled_for_soft_delete": { + Type: schema.TypeBool, + Computed: true, + }, + + "enabled_for_purge_protection": { + Type: schema.TypeBool, + Computed: true, + }, + "tags": tags.SchemaDataSource(), }, } @@ -190,6 +200,8 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) + d.Set("enabled_for_soft_delete", props.EnableSoftDelete) + d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index a8dee0b0b0fa..2b6cbf0ffbc0 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -157,6 +157,25 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, }, + "enabled_for_soft_delete": { + Type: schema.TypeBool, + Optional: true, + ValidateFunc: validate.BoolIsTrue(), + }, + + "enabled_for_purge_protection": { + Type: schema.TypeBool, + Optional: true, + ValidateFunc: validate.BoolIsTrue(), + }, + + "purge_on_delete": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ConflictsWith: []string{"enabled_for_purge_protection"}, + }, + "network_acls": { Type: schema.TypeList, Optional: true, @@ -252,6 +271,8 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) + enabledForSoftDelete := d.Get("enabled_for_soft_delete").(bool) + enabledForPurgeProtection := d.Get("enabled_for_purge_protection").(bool) t := d.Get("tags").(map[string]interface{}) networkAclsRaw := d.Get("network_acls").([]interface{}) @@ -277,6 +298,16 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e Tags: tags.Expand(t), } + // This setting can only be set if it is true, if set when value is false API returns errors + if enabledForSoftDelete { + parameters.Properties.EnableSoftDelete = &enabledForSoftDelete + } + + // This setting can only be set if it is true, if set when value is false API returns errors + if enabledForPurgeProtection { + parameters.Properties.EnablePurgeProtection = &enabledForPurgeProtection + } + // Locking this resource so we don't make modifications to it at the same time if there is a // key vault access policy trying to update it as well locks.ByName(name, keyVaultResourceName) @@ -375,6 +406,8 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) + d.Set("enabled_for_soft_delete", props.EnableSoftDelete) + d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { @@ -461,6 +494,22 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } + if d.Get("purge_on_delete").(bool) && d.Get("enabled_for_soft_delete").(bool) && err == nil { + log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) + location := d.Get("location").(string) + + future, err := client.PurgeDeleted(ctx, name, location) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for purge of KeyVault %s", name) + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error purging Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + return nil } diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index 8953c5d82eff..9b33dc5a66be 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -111,6 +111,26 @@ func TestAccDataSourceAzureRMKeyVault_networkAcls(t *testing.T) { }) } +func TestAccDataSourceAzureRMKeyVault_enable_soft_delete(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_key_vault", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMKeyVault_enable_soft_delete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_soft_delete", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_purge_protection", "false"), + ), + }, + }, + }) +} + func testAccDataSourceAzureRMKeyVault_basic(data acceptance.TestData) string { r := testAccAzureRMKeyVault_basic(data) return fmt.Sprintf(` @@ -146,3 +166,15 @@ data "azurerm_key_vault" "test" { } `, r) } + +func testAccDataSourceAzureRMKeyVault_enable_soft_delete(data acceptance.TestData) string { + r := testAccAzureRMKeyVault_enable_soft_delete(data) + return fmt.Sprintf(` +%s + +data "azurerm_key_vault" "test" { + name = "${azurerm_key_vault.test.name}" + resource_group_name = "${azurerm_key_vault.test.resource_group_name}" +} +`, r) +} diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index e492e07ba1e3..2bc04b0b1b12 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -331,6 +331,55 @@ func TestAccAzureRMKeyVault_justCert(t *testing.T) { }) } +func TestAccAzureRMKeyVault_soft_delete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + expectedError := fmt.Sprintf("^Check failed: Check 1/1 error: Not found: %s", data.ResourceName) + errorRegEx, _ := regexp.Compile(expectedError) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), + ), + }, + { + Config: testAccAzureRMKeyVault_enable_soft_delete(data), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "get"), + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "get"), + resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_soft_delete", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_purge_protection", "false"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), + ), + }, + { + Config: testAccAzureRMKeyVault_purge_vault(data), + ExpectError: errorRegEx, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + ), + }, + { + Config: testAccAzureRMKeyVault_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), + resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), + resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), + ), + }, + }, + }) +} + func testCheckAzureRMKeyVaultDestroy(s *terraform.State) error { client := acceptance.AzureProvider.Meta().(*clients.Client).KeyVault.VaultsClient ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext @@ -446,6 +495,10 @@ resource "azurerm_key_vault" "test" { "set", ] } + + tags = { + environment = "Production" + } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } @@ -787,6 +840,59 @@ resource "azurerm_key_vault" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func testAccAzureRMKeyVault_enable_soft_delete(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + + sku { + name = "premium" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.client_id}" + + key_permissions = [ + "get", + ] + + secret_permissions = [ + "get", + ] + } + + enabled_for_soft_delete = true + purge_on_delete = true + + tags = { + environment = "Production" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMKeyVault_purge_vault(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} + func testAccAzureRMKeyVault_complete(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/website/docs/d/key_vault.html.markdown b/website/docs/d/key_vault.html.markdown index 26314d208129..cdb96e02f290 100644 --- a/website/docs/d/key_vault.html.markdown +++ b/website/docs/d/key_vault.html.markdown @@ -54,6 +54,10 @@ The following attributes are exported: * `enabled_for_template_deployment` - Can Azure Resource Manager retrieve secrets from the Key Vault? +* `enabled_for_soft_delete` - Is soft delete enabled on this Key Vault? + +* `enabled_for_purge_protection` - Is purge protection enabled on this Key Vault? + * `tags` - A mapping of tags assigned to the Key Vault. A `sku` block exports the following: diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index 6d257aa5ad83..ed02b1c1903b 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -84,6 +84,12 @@ The following arguments are supported: * `enabled_for_template_deployment` - (Optional) Boolean flag to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault. Defaults to `false`. +* `enabled_for_soft_delete` - (Optional) Boolean flag to specify whether the key vault is enabled for soft delete. Once enabled you can not disable this setting. + +* `purge_on_delete` - (Optional) Boolean flag to specify if the KeyVault should be purged on delete. This purges KeyVaults enabled for soft delete on resource deletition. + +* `enabled_for_purge_protection` - (Optional) Boolean flag to specify whether the key vault is enabled for purge protection, this conflicts with `purge_on_delete`. Once enabled you can not disable this setting. + * `network_acls` - (Optional) A `network_acls` block as defined below. * `tags` - (Optional) A mapping of tags to assign to the resource. From beafb4c57f0159bcb929024384f7ed21c7fe652d Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 8 Jan 2020 14:06:07 -0800 Subject: [PATCH 02/33] fix lint errors --- azurerm/helpers/validate/bool_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/azurerm/helpers/validate/bool_test.go b/azurerm/helpers/validate/bool_test.go index d9ba6b9627a9..d378dafc41b0 100644 --- a/azurerm/helpers/validate/bool_test.go +++ b/azurerm/helpers/validate/bool_test.go @@ -3,7 +3,6 @@ package validate import "testing" func TestBoolIsTrue(t *testing.T) { - testCases := []struct { Value bool ShouldHaveError bool @@ -31,7 +30,6 @@ func TestBoolIsTrue(t *testing.T) { t.Fatalf("Expected %t to return no errors, but got some %+v", value.Value, errors) return } - } }) -} \ No newline at end of file +} From 70a6a1815e7168be3c8a3e4be59f2e43e1fef880 Mon Sep 17 00:00:00 2001 From: WS <20408400+WodansSon@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:16:44 -0800 Subject: [PATCH 03/33] Update website/docs/r/key_vault.html.markdown Co-Authored-By: Tom Harvey --- website/docs/r/key_vault.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index ed02b1c1903b..c4089524fc73 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -88,7 +88,9 @@ The following arguments are supported: * `purge_on_delete` - (Optional) Boolean flag to specify if the KeyVault should be purged on delete. This purges KeyVaults enabled for soft delete on resource deletition. -* `enabled_for_purge_protection` - (Optional) Boolean flag to specify whether the key vault is enabled for purge protection, this conflicts with `purge_on_delete`. Once enabled you can not disable this setting. +* `enabled_for_purge_protection` - (Optional) Is Purge Protection enabled for this Key Vault? + +-> **NOTE:** Once Purge Protection has been Enabled there's currently no way to Disable it - [support for this is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075) * `network_acls` - (Optional) A `network_acls` block as defined below. From 22701db8ce57618d3f8a0f7834e779afc2f19764 Mon Sep 17 00:00:00 2001 From: WS <20408400+WodansSon@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:17:10 -0800 Subject: [PATCH 04/33] Update website/docs/r/key_vault.html.markdown Co-Authored-By: Tom Harvey --- website/docs/r/key_vault.html.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index c4089524fc73..b9b1364f5d90 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -84,7 +84,9 @@ The following arguments are supported: * `enabled_for_template_deployment` - (Optional) Boolean flag to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault. Defaults to `false`. -* `enabled_for_soft_delete` - (Optional) Boolean flag to specify whether the key vault is enabled for soft delete. Once enabled you can not disable this setting. +* `enabled_for_soft_delete` - (Optional) Should Soft Delete be enabled for items in this Key Vault? + +-> **NOTE:** Once enabled you can not disable this setting. * `purge_on_delete` - (Optional) Boolean flag to specify if the KeyVault should be purged on delete. This purges KeyVaults enabled for soft delete on resource deletition. From 50563f8546f9cbff7908d31e885c924256420c7e Mon Sep 17 00:00:00 2001 From: WS <20408400+WodansSon@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:22:24 -0800 Subject: [PATCH 05/33] Update azurerm/internal/services/keyvault/resource_arm_key_vault.go Co-Authored-By: Tom Harvey --- azurerm/internal/services/keyvault/resource_arm_key_vault.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 2b6cbf0ffbc0..ff00aa44c20c 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -160,7 +160,6 @@ func resourceArmKeyVault() *schema.Resource { "enabled_for_soft_delete": { Type: schema.TypeBool, Optional: true, - ValidateFunc: validate.BoolIsTrue(), }, "enabled_for_purge_protection": { From 68f55a9f71ce98fd658a3a201df293c07bd8d79b Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 31 Jan 2020 19:01:28 -0800 Subject: [PATCH 06/33] WIP Get value from provider --- azurerm/internal/features/user_flags.go | 19 +++ azurerm/internal/provider/features.go | 116 ++++++++++++++++++ azurerm/internal/provider/provider.go | 3 + .../keyvault/resource_arm_key_vault.go | 98 +++------------ 4 files changed, 156 insertions(+), 80 deletions(-) create mode 100644 azurerm/internal/features/user_flags.go create mode 100644 azurerm/internal/provider/features.go diff --git a/azurerm/internal/features/user_flags.go b/azurerm/internal/features/user_flags.go new file mode 100644 index 000000000000..834b6bc903c3 --- /dev/null +++ b/azurerm/internal/features/user_flags.go @@ -0,0 +1,19 @@ +package features + +type UserFeatures struct { + VirtualMachine VirtualMachineFeatures + VirtualMachineScaleSet VirtualMachineScaleSetFeatures + KeyVault KeyVaultFeatures +} + +type VirtualMachineFeatures struct { + DeleteOSDiskOnDeletion bool +} + +type VirtualMachineScaleSetFeatures struct { + RollInstancesWhenRequired bool +} + +type KeyVaultFeatures struct { + PurgeSoftDeleteOnDestroy bool +} diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go new file mode 100644 index 000000000000..b2fc42cb0536 --- /dev/null +++ b/azurerm/internal/provider/features.go @@ -0,0 +1,116 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" +) + +func schemaFeatures() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + // TODO: make this Required in 2.0 + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "virtual_machine": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delete_os_disk_on_deletion": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + + "virtual_machine_scale_set": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "roll_instances_when_required": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + + "key_vault": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "purge_soft_delete_on_destroy": { + Type: schema.TypeBool, + Required: true, + }, + }, + }, + }, + }, + }, + } +} + +func expandFeatures(input []interface{}) features.UserFeatures { + // TODO: in 2.0 when Required this can become: + //val := input[0].(map[string]interface{}) + + var val map[string]interface{} + if len(input) > 0 { + val = input[0].(map[string]interface{}) + } + + // these are the defaults if omitted from the config + features := features.UserFeatures{ + // NOTE: ensure all nested objects are fully populated + VirtualMachine: features.VirtualMachineFeatures{ + DeleteOSDiskOnDeletion: true, + }, + VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ + RollInstancesWhenRequired: true, + }, + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, + } + + if raw, ok := val["virtual_machine"]; ok { + items := raw.([]interface{}) + if len(items) > 0 { + virtualMachinesRaw := items[0].(map[string]interface{}) + if v, ok := virtualMachinesRaw["delete_os_disk_on_deletion"]; ok { + features.VirtualMachine.DeleteOSDiskOnDeletion = v.(bool) + } + } + } + + if raw, ok := val["virtual_machine_scale_set"]; ok { + items := raw.([]interface{}) + if len(items) > 0 { + scaleSetRaw := items[0].(map[string]interface{}) + if v, ok := scaleSetRaw["roll_instances_when_required"]; ok { + features.VirtualMachineScaleSet.RollInstancesWhenRequired = v.(bool) + } + } + } + + if raw, ok := val["key_vault"]; ok { + items := raw.([]interface{}) + if len(items) > 0 { + keyVaultRaw := items[0].(map[string]interface{}) + if v, ok := keyVaultRaw["purge_soft_delete_on_destroy"]; ok { + features.KeyVault.PurgeSoftDeleteOnDestroy = v.(bool) + } + } + } + + return features +} diff --git a/azurerm/internal/provider/provider.go b/azurerm/internal/provider/provider.go index 6b3f8e263a1f..44d4c8056f3d 100644 --- a/azurerm/internal/provider/provider.go +++ b/azurerm/internal/provider/provider.go @@ -159,6 +159,8 @@ func AzureProvider() terraform.ResourceProvider { Description: "This will disable the Terraform Partner ID which is used if a custom `partner_id` isn't specified.", }, + "features": schemaFeatures(), + // Advanced feature flags "skip_credentials_validation": { Type: schema.TypeBool, @@ -282,6 +284,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureFunc { } } + const resourceProviderRegistrationErrorFmt = `Error ensuring Resource Providers are registered. Terraform automatically attempts to register the Resource Providers it supports to diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index ff00aa44c20c..d0376f4717f1 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -67,28 +67,6 @@ func resourceArmKeyVault() *schema.Resource { "resource_group_name": azure.SchemaResourceGroupName(), - // Remove in 2.0 - "sku": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Deprecated: "This property has been deprecated in favour of the 'sku_name' property and will be removed in version 2.0 of the provider", - ConflictsWith: []string{"sku_name"}, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - string(keyvault.Standard), - string(keyvault.Premium), - }, false), - }, - }, - }, - }, - "sku_name": { Type: schema.TypeString, Optional: true, @@ -96,8 +74,7 @@ func resourceArmKeyVault() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{ string(keyvault.Standard), string(keyvault.Premium), - // TODO: revert this in 2.0 - }, true), + }, false), }, "vault_uri": { @@ -157,22 +134,9 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, }, - "enabled_for_soft_delete": { - Type: schema.TypeBool, - Optional: true, - }, - - "enabled_for_purge_protection": { - Type: schema.TypeBool, - Optional: true, - ValidateFunc: validate.BoolIsTrue(), - }, - - "purge_on_delete": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ConflictsWith: []string{"enabled_for_purge_protection"}, + "soft_delete_enabled": { + Type: schema.TypeBool, + Optional: true, }, "network_acls": { @@ -224,29 +188,6 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - // Remove in 2.0 - var sku keyvault.Sku - - if inputs := d.Get("sku").([]interface{}); len(inputs) != 0 { - input := inputs[0].(map[string]interface{}) - v := input["name"].(string) - - sku = keyvault.Sku{ - Family: &armKeyVaultSkuFamily, - Name: keyvault.SkuName(v), - } - } else { - // Keep in 2.0 - sku = keyvault.Sku{ - Family: &armKeyVaultSkuFamily, - Name: keyvault.SkuName(d.Get("sku_name").(string)), - } - } - - if sku.Name == "" { - return fmt.Errorf("either 'sku_name' or 'sku' must be defined in the configuration file") - } - log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.") name := d.Get("name").(string) @@ -265,12 +206,18 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e } } + purgeSoftDeleteOnDestroy := meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy + + if purgeSoftDeleteOnDestroy { + log.Printf("[INFO] purgeSoftDeleteOnDestroy is true") + } + location := azure.NormalizeLocation(d.Get("location").(string)) tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) - enabledForSoftDelete := d.Get("enabled_for_soft_delete").(bool) + enabledForSoftDelete := d.Get("soft_delete_enabled").(bool) enabledForPurgeProtection := d.Get("enabled_for_purge_protection").(bool) t := d.Get("tags").(map[string]interface{}) @@ -283,6 +230,11 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error expanding `access_policy`: %+v", policies) } + sku := keyvault.Sku{ + Family: &armKeyVaultSkuFamily, + Name: keyvault.SkuName(d.Get("sku_name").(string)), + } + parameters := keyvault.VaultCreateOrUpdateParameters{ Location: &location, Properties: &keyvault.VaultProperties{ @@ -405,16 +357,11 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) - d.Set("enabled_for_soft_delete", props.EnableSoftDelete) + d.Set("soft_delete_enabled", props.EnableSoftDelete) d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { - // Remove in 2.0 - if err := d.Set("sku", flattenKeyVaultSku(sku)); err != nil { - return fmt.Errorf("Error setting 'sku' for KeyVault %q: %+v", *resp.Name, err) - } - if err := d.Set("sku_name", string(sku.Name)); err != nil { return fmt.Errorf("Error setting 'sku_name' for KeyVault %q: %+v", *resp.Name, err) } @@ -493,7 +440,7 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } - if d.Get("purge_on_delete").(bool) && d.Get("enabled_for_soft_delete").(bool) && err == nil { + if d.Get("purge_on_delete").(bool) && d.Get("soft_delete_enabled").(bool) && err == nil { log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) location := d.Get("location").(string) @@ -512,15 +459,6 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { return nil } -// Remove in 2.0 -func flattenKeyVaultSku(sku *keyvault.Sku) []interface{} { - result := map[string]interface{}{ - "name": string(sku.Name), - } - - return []interface{}{result} -} - func flattenKeyVaultNetworkAcls(input *keyvault.NetworkRuleSet) []interface{} { if input == nil { return []interface{}{ From 924c38a465038e6762fd97f4ce3cf8d5c3fdba07 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Mon, 3 Feb 2020 18:21:32 -0800 Subject: [PATCH 07/33] Done except for purge on destroy --- azurerm/helpers/azure/key_vault.go | 17 ++++++++++ .../keyvault/resource_arm_key_vault.go | 33 +++++++++---------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/azurerm/helpers/azure/key_vault.go b/azurerm/helpers/azure/key_vault.go index 7957380bb594..3ab6c2340fe8 100644 --- a/azurerm/helpers/azure/key_vault.go +++ b/azurerm/helpers/azure/key_vault.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -118,3 +119,19 @@ func KeyVaultExists(ctx context.Context, client *keyvault.VaultsClient, keyVault return true, nil } + +func KeyVaultCustomizeDiff(d *schema.ResourceDiff, _ interface{}) error { + if d.HasChange("soft_delete_enabled") { + if old, new := d.GetChange("soft_delete_enabled"); old.(bool) && !new.(bool) { + return fmt.Errorf("the property 'soft_delete_enabled' cannot be set to false, enabling the soft delete for a vault is an irreversible action") + } + } + + if d.HasChange("purge_protection_enabled") { + if old, new := d.GetChange("purge_protection_enabled"); old.(bool) && !new.(bool) { + return fmt.Errorf("the property 'purge_protection_enabled' cannot be set to false, enabling the purge protection for a vault is an irreversible action") + } + } + + return nil +} diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 8d6448d1b456..736a66d8eb95 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -138,6 +138,11 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, }, + "purge_protection_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "network_acls": { Type: schema.TypeList, Optional: true, @@ -179,6 +184,8 @@ func resourceArmKeyVault() *schema.Resource { "tags": tags.Schema(), }, + + CustomizeDiff: azure.KeyVaultCustomizeDiff, } } @@ -204,20 +211,14 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e return tf.ImportAsExistsError("azurerm_key_vault", *existing.ID) } } - - purgeSoftDeleteOnDestroy := meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy - - if purgeSoftDeleteOnDestroy { - log.Printf("[INFO] purgeSoftDeleteOnDestroy is true") - } - location := azure.NormalizeLocation(d.Get("location").(string)) tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) - enabledForSoftDelete := d.Get("soft_delete_enabled").(bool) - enabledForPurgeProtection := d.Get("enabled_for_purge_protection").(bool) + softDeleteEnabled := d.Get("soft_delete_enabled").(bool) + purgeProtectionEnabled := d.Get("purge_protection_enabled").(bool) + t := d.Get("tags").(map[string]interface{}) networkAclsRaw := d.Get("network_acls").([]interface{}) @@ -248,14 +249,12 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e Tags: tags.Expand(t), } - // This setting can only be set if it is true, if set when value is false API returns errors - if enabledForSoftDelete { - parameters.Properties.EnableSoftDelete = &enabledForSoftDelete + // This settings can only be set if it is true, if set when value is false API returns errors + if softDeleteEnabled { + parameters.Properties.EnableSoftDelete = &softDeleteEnabled } - - // This setting can only be set if it is true, if set when value is false API returns errors - if enabledForPurgeProtection { - parameters.Properties.EnablePurgeProtection = &enabledForPurgeProtection + if purgeProtectionEnabled { + parameters.Properties.EnablePurgeProtection = &purgeProtectionEnabled } // Locking this resource so we don't make modifications to it at the same time if there is a @@ -357,7 +356,7 @@ func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) d.Set("soft_delete_enabled", props.EnableSoftDelete) - d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) + d.Set("purge_protection_enabled", props.EnablePurgeProtection) d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { From 0f89dbde8fa1aa78bd8560fcdfaf6fb26d64e9ee Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Wed, 5 Feb 2020 16:24:10 -0800 Subject: [PATCH 08/33] Working soft delete and purge --- .../keyvault/data_source_key_vault.go | 8 ++--- .../keyvault/resource_arm_key_vault.go | 35 +++++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/azurerm/internal/services/keyvault/data_source_key_vault.go b/azurerm/internal/services/keyvault/data_source_key_vault.go index 6117855e4e12..82ecb8e5f06e 100644 --- a/azurerm/internal/services/keyvault/data_source_key_vault.go +++ b/azurerm/internal/services/keyvault/data_source_key_vault.go @@ -156,12 +156,12 @@ func dataSourceArmKeyVault() *schema.Resource { }, }, - "enabled_for_soft_delete": { + "purge_protection_enabled": { Type: schema.TypeBool, Computed: true, }, - "enabled_for_purge_protection": { + "soft_delete_enabled": { Type: schema.TypeBool, Computed: true, }, @@ -200,8 +200,8 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("enabled_for_deployment", props.EnabledForDeployment) d.Set("enabled_for_disk_encryption", props.EnabledForDiskEncryption) d.Set("enabled_for_template_deployment", props.EnabledForTemplateDeployment) - d.Set("enabled_for_soft_delete", props.EnableSoftDelete) - d.Set("enabled_for_purge_protection", props.EnablePurgeProtection) + d.Set("soft_delete_enabled", props.EnableSoftDelete) + d.Set("purge_protection_enabled", props.EnablePurgeProtection) d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 736a66d8eb95..eb7207f76fbc 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -133,16 +133,6 @@ func resourceArmKeyVault() *schema.Resource { Optional: true, }, - "soft_delete_enabled": { - Type: schema.TypeBool, - Optional: true, - }, - - "purge_protection_enabled": { - Type: schema.TypeBool, - Optional: true, - }, - "network_acls": { Type: schema.TypeList, Optional: true, @@ -182,6 +172,16 @@ func resourceArmKeyVault() *schema.Resource { }, }, + "purge_protection_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + + "soft_delete_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "tags": tags.Schema(), }, @@ -211,7 +211,20 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e return tf.ImportAsExistsError("azurerm_key_vault", *existing.ID) } } + + // before creating check to see if the key vault exists in the soft delete state location := azure.NormalizeLocation(d.Get("location").(string)) + softDel, err := client.GetDeleted(ctx, name, location) + if err != nil && !utils.ResponseWasNotFound(softDel.Response) { + return fmt.Errorf("Error retrieving soft deleted Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if props := softDel.Properties; props != nil { + delDate := (*props.DeletionDate).Format(time.RFC3339) + purgeDate := (*props.ScheduledPurgeDate).Format(time.RFC3339) + return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists in the soft delete state. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate, purgeDate) + } + tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) @@ -438,7 +451,7 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } - if d.Get("purge_on_delete").(bool) && d.Get("soft_delete_enabled").(bool) && err == nil { + if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy { log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) location := d.Get("location").(string) From 023aa56a2bfaac84ff642f09216314c3b7b5c5f6 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 6 Feb 2020 16:53:48 -0800 Subject: [PATCH 09/33] Complete --- azurerm/helpers/azure/key_vault.go | 28 ++++ azurerm/internal/provider/features.go | 2 +- azurerm/internal/provider/provider.go | 1 - .../keyvault/resource_arm_key_vault.go | 37 +++-- .../tests/data_source_key_vault_test.go | 11 +- .../tests/resource_arm_key_vault_test.go | 142 ++++++++---------- website/docs/d/key_vault.html.markdown | 4 +- website/docs/index.html.markdown | 10 ++ website/docs/r/key_vault.html.markdown | 23 ++- 9 files changed, 157 insertions(+), 101 deletions(-) diff --git a/azurerm/helpers/azure/key_vault.go b/azurerm/helpers/azure/key_vault.go index 3ab6c2340fe8..f75368e5d237 100644 --- a/azurerm/helpers/azure/key_vault.go +++ b/azurerm/helpers/azure/key_vault.go @@ -3,6 +3,7 @@ package azure import ( "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -135,3 +136,30 @@ func KeyVaultCustomizeDiff(d *schema.ResourceDiff, _ interface{}) error { return nil } + +func KeyVaultGetSoftDeletedState(ctx context.Context, client *keyvault.VaultsClient, name string, location string) (deleteDate interface{}, purgeDate interface{}, err error) { + softDel, err := client.GetDeleted(ctx, name, location) + if err != nil { + return nil, nil, fmt.Errorf("unable to get soft delete state information: %+v", err) + } + + // the logic is this way because the GetDeleted call will return an existing key vault + // that is not soft deleted, but the Deleted Vault properties will be nil + if props := softDel.Properties; props != nil { + var delDate interface{} + var purgeDate interface{} + + if dd := props.DeletionDate; dd != nil { + delDate = (*dd).Format(time.RFC3339) + } + if pg := props.ScheduledPurgeDate; pg != nil { + purgeDate = (*pg).Format(time.RFC3339) + } + if delDate != nil && purgeDate != nil { + return delDate, purgeDate, nil + } + } + + // this means we found an existing key vault that is not soft deleted + return nil, nil, nil +} diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 9c0321188939..458d6d9b3515 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -78,7 +78,7 @@ func expandFeatures(input []interface{}) features.UserFeatures { RollInstancesWhenRequired: true, }, KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: false, + PurgeSoftDeleteOnDestroy: true, }, } diff --git a/azurerm/internal/provider/provider.go b/azurerm/internal/provider/provider.go index 77dc218e5140..b59f0af295a3 100644 --- a/azurerm/internal/provider/provider.go +++ b/azurerm/internal/provider/provider.go @@ -285,7 +285,6 @@ func providerConfigure(p *schema.Provider) schema.ConfigureFunc { } } - const resourceProviderRegistrationErrorFmt = `Error ensuring Resource Providers are registered. Terraform automatically attempts to register the Resource Providers it supports to diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index eb7207f76fbc..6d1772756f0b 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -198,6 +198,7 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) + purgeProtectionEnabled := d.Get("purge_protection_enabled").(bool) if features.ShouldResourcesBeImported() && d.IsNewResource() { existing, err := client.Get(ctx, resourceGroup, name) @@ -214,15 +215,17 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e // before creating check to see if the key vault exists in the soft delete state location := azure.NormalizeLocation(d.Get("location").(string)) - softDel, err := client.GetDeleted(ctx, name, location) - if err != nil && !utils.ResponseWasNotFound(softDel.Response) { - return fmt.Errorf("Error retrieving soft deleted Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) + if err == nil { + if delDate != nil || purgeDate != nil { + return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists in the soft delete state. The key vault was soft deleted on %s and is scheduled to be purged on %s, please use the Azure CLI tool to recover this Key Vault", name, resourceGroup, delDate.(string), purgeDate.(string)) + } + return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists", name, resourceGroup) } - if props := softDel.Properties; props != nil { - delDate := (*props.DeletionDate).Format(time.RFC3339) - purgeDate := (*props.ScheduledPurgeDate).Format(time.RFC3339) - return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists in the soft delete state. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate, purgeDate) + // check to see if they enabled purge protection and purge on destroy + if purgeProtectionEnabled && meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy { + return fmt.Errorf("conflicting properties 'purge_protection_enabled' and 'purge_soft_delete_on_destroy' are both enabled for Key Vault %q (Resource Group %q). Please disable one of these paroperties and re-apply", name, resourceGroup) } tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) @@ -230,7 +233,6 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) softDeleteEnabled := d.Get("soft_delete_enabled").(bool) - purgeProtectionEnabled := d.Get("purge_protection_enabled").(bool) t := d.Get("tags").(map[string]interface{}) @@ -417,6 +419,12 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) } + // Check to see if purge protection is enabled or not... + purgeProtectionEnabled := false + if ppe := read.Properties.EnablePurgeProtection; ppe != nil { + purgeProtectionEnabled = *ppe + } + // ensure we lock on the latest network names, to ensure we handle Azure's networking layer being limited to one change at a time virtualNetworkNames := make([]string, 0) if props := read.Properties; props != nil { @@ -452,9 +460,20 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy { - log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) location := d.Get("location").(string) + // check to see if purge protection is enabled or not + if purgeProtectionEnabled { + delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) + if err == nil { + if delDate != nil && purgeDate != nil { + return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate.(string), purgeDate.(string)) + } + return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled", name, resourceGroup) + } + } + + log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) future, err := client.PurgeDeleted(ctx, name, location) if err != nil { return err diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index 9b33dc5a66be..18e6526a428d 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -111,7 +111,7 @@ func TestAccDataSourceAzureRMKeyVault_networkAcls(t *testing.T) { }) } -func TestAccDataSourceAzureRMKeyVault_enable_soft_delete(t *testing.T) { +func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_key_vault", "test") resource.Test(t, resource.TestCase{ @@ -123,10 +123,13 @@ func TestAccDataSourceAzureRMKeyVault_enable_soft_delete(t *testing.T) { Config: testAccDataSourceAzureRMKeyVault_enable_soft_delete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_soft_delete", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_purge_protection", "false"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), ), }, + { + Config: testAccAzureRMKeyVault_softDeletePurge(data), + }, }, }) } @@ -168,7 +171,7 @@ data "azurerm_key_vault" "test" { } func testAccDataSourceAzureRMKeyVault_enable_soft_delete(data acceptance.TestData) string { - r := testAccAzureRMKeyVault_enable_soft_delete(data) + r := testAccAzureRMKeyVault_softDelete(data) return fmt.Sprintf(` %s diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 94912adf292a..087882a0fe7f 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -331,50 +331,35 @@ func TestAccAzureRMKeyVault_justCert(t *testing.T) { }) } -func TestAccAzureRMKeyVault_soft_delete(t *testing.T) { +func TestAccAzureRMKeyVault_softDelete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") - expectedError := fmt.Sprintf("^Check failed: Check 1/1 error: Not found: %s", data.ResourceName) - errorRegEx, _ := regexp.Compile(expectedError) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acceptance.PreCheck(t) }, Providers: acceptance.SupportedProviders, CheckDestroy: testCheckAzureRMKeyVaultDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMKeyVault_basic(data), + Config: testAccAzureRMKeyVault_softDelete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), ), }, { - Config: testAccAzureRMKeyVault_enable_soft_delete(data), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "get"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "get"), - resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_soft_delete", "true"), - resource.TestCheckResourceAttr(data.ResourceName, "enabled_for_purge_protection", "false"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), - ), + Config: testAccAzureRMKeyVault_softDeletePurge(data), }, { - Config: testAccAzureRMKeyVault_purge_vault(data), - ExpectError: errorRegEx, + Config: testAccAzureRMKeyVault_softDelete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), ), }, { - Config: testAccAzureRMKeyVault_basic(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMKeyVaultExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.environment", "Production"), - ), + Config: testAccAzureRMKeyVault_softDeletePurge(data), }, }, }) @@ -840,59 +825,6 @@ resource "azurerm_key_vault" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func testAccAzureRMKeyVault_enable_soft_delete(data acceptance.TestData) string { - return fmt.Sprintf(` -data "azurerm_client_config" "current" {} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_key_vault" "test" { - name = "vault%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - - sku { - name = "premium" - } - - access_policy { - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - object_id = "${data.azurerm_client_config.current.client_id}" - - key_permissions = [ - "get", - ] - - secret_permissions = [ - "get", - ] - } - - enabled_for_soft_delete = true - purge_on_delete = true - - tags = { - environment = "Production" - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - -func testAccAzureRMKeyVault_purge_vault(data acceptance.TestData) string { - return fmt.Sprintf(` -data "azurerm_client_config" "current" {} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} -`, data.RandomInteger, data.Locations.Primary) -} - func testAccAzureRMKeyVault_complete(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} @@ -1036,3 +968,57 @@ access_policy { } `, oid) } + +func testAccAzureRMKeyVault_softDelete(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +provider "azurerm" { + alias = "keyVault" + + features { + key_vault { + purge_soft_delete_on_destroy = true + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + provider = azurerm.keyVault + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + soft_delete_enabled = true + purge_protection_enabled = false + + sku_name = "premium" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMKeyVault_softDeletePurge(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +provider "azurerm" { + alias = "keyVault" + + features { + key_vault { + purge_soft_delete_on_destroy = true + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} diff --git a/website/docs/d/key_vault.html.markdown b/website/docs/d/key_vault.html.markdown index 73bb65e0f423..40cf255b2744 100644 --- a/website/docs/d/key_vault.html.markdown +++ b/website/docs/d/key_vault.html.markdown @@ -53,9 +53,9 @@ The following attributes are exported: * `enabled_for_template_deployment` - Can Azure Resource Manager retrieve secrets from the Key Vault? -* `enabled_for_soft_delete` - Is soft delete enabled on this Key Vault? +* `soft_delete_enabled` - Is soft delete enabled on this Key Vault? -* `enabled_for_purge_protection` - Is purge protection enabled on this Key Vault? +* `purge_protection_enabled` - Is purge protection enabled on this Key Vault? * `tags` - A mapping of tags assigned to the Key Vault. diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 5732fa2caeac..310b2c4def1b 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -149,12 +149,22 @@ It's possible to customise the behaviour of certain Azure Provider resources usi The `features` block supports the following: +* `key_vault` - (Optional) A `key_vault` block as defined below. + * `virtual_machine` - (Optional) A `virtual_machine` block as defined below. * `virtual_machine_scale_set` - (Optional) A `virtual_machine_scale_set` block as defined below. --- +The `key_vault` block supports the following: + +* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. + +~> **Note:** When purge protection is enabled, a vault or an object in the deleted state cannot be purged until the retention period has passed. + +--- + The `virtual_machine` block supports the following: * `delete_os_disk_on_deletion` - (Optional) Should the `azurerm_linux_virtual_machine` and `azurerm_windows_virtual_machine` resources delete the OS Disk attached to the Virtual Machine when the Virtual Machine is destroyed? Defaults to `true`. diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index 9bf8807beb99..4a97eac0e3d7 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -15,6 +15,16 @@ Manages a Key Vault. ## Example Usage ```hcl +provider "azurerm" { + alias = "keyVault" + + features { + key_vault { + purge_soft_delete_on_destroy = true + } + } +} + resource "azurerm_resource_group" "example" { name = "resourceGroup1" location = "West US" @@ -22,10 +32,13 @@ resource "azurerm_resource_group" "example" { resource "azurerm_key_vault" "example" { name = "testvault" + provider = azurerm.keyVault location = "${azurerm_resource_group.example.location}" resource_group_name = "${azurerm_resource_group.example.name}" enabled_for_disk_encryption = true tenant_id = "d6e396d0-5584-41dc-9fc0-268df99bc610" + soft_delete_enabled = true + purge_protection_enabled = false sku_name = "standard" @@ -83,15 +96,13 @@ The following arguments are supported: * `enabled_for_template_deployment` - (Optional) Boolean flag to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault. Defaults to `false`. -* `enabled_for_soft_delete` - (Optional) Should Soft Delete be enabled for items in this Key Vault? - --> **NOTE:** Once enabled you can not disable this setting. +* `soft_delete_enabled` - (Optional) Should Soft Delete be enabled for this Key Vault? -* `purge_on_delete` - (Optional) Boolean flag to specify if the KeyVault should be purged on delete. This purges KeyVaults enabled for soft delete on resource deletition. +-> **NOTE:** Once `soft_delete_enabled` has been enabled it is an **irreversible** action. If you want to destroy this key vault before the 90 day purge policy expires you must set the `purge_soft_delete_on_destroy` to **true** in the `key_vault` section of the azure provider `features` block. -* `enabled_for_purge_protection` - (Optional) Is Purge Protection enabled for this Key Vault? +* `purge_protection_enabled` - (Optional) Is Purge Protection enabled for this Key Vault? --> **NOTE:** Once Purge Protection has been Enabled there's currently no way to Disable it - [support for this is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075) +-> **NOTE:** Once `purge_protection_enabled` has been enabled it is an **irreversible** action. - [support for this is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075) * `network_acls` - (Optional) A `network_acls` block as defined below. From 7b9909c21d2e675a21ea074d90b3f308ecff139b Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 6 Feb 2020 16:59:06 -0800 Subject: [PATCH 10/33] updated or to and --- azurerm/internal/services/keyvault/resource_arm_key_vault.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 6d1772756f0b..2b6429646558 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -217,7 +217,7 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e location := azure.NormalizeLocation(d.Get("location").(string)) delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) if err == nil { - if delDate != nil || purgeDate != nil { + if delDate != nil && purgeDate != nil { return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists in the soft delete state. The key vault was soft deleted on %s and is scheduled to be purged on %s, please use the Azure CLI tool to recover this Key Vault", name, resourceGroup, delDate.(string), purgeDate.(string)) } return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists", name, resourceGroup) From 96a36df0b80f1ea75638635f60e5fde6c15ce3ee Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 6 Feb 2020 18:45:24 -0800 Subject: [PATCH 11/33] Fix features test cases --- azurerm/internal/provider/features_test.go | 111 ++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index 09be0cd828f3..41041e6d1e34 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -24,10 +24,13 @@ func TestExpandFeatures(t *testing.T) { VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, }, + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, }, }, { - Name: "Complete", + Name: "Complete Enabled", Input: []interface{}{ map[string]interface{}{ "virtual_machine": []interface{}{ @@ -40,6 +43,11 @@ func TestExpandFeatures(t *testing.T) { "roll_instances_when_required": true, }, }, + "key_vault": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": true, + }, + }, }, }, Expected: features.UserFeatures{ @@ -49,6 +57,42 @@ func TestExpandFeatures(t *testing.T) { VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, }, + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, + }, + }, + { + Name: "Complete Disabled", + Input: []interface{}{ + map[string]interface{}{ + "virtual_machine": []interface{}{ + map[string]interface{}{ + "delete_os_disk_on_deletion": false, + }, + }, + "virtual_machine_scale_set": []interface{}{ + map[string]interface{}{ + "roll_instances_when_required": false, + }, + }, + "key_vault": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": false, + }, + }, + }, + }, + Expected: features.UserFeatures{ + VirtualMachine: features.VirtualMachineFeatures{ + DeleteOSDiskOnDeletion: false, + }, + VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ + RollInstancesWhenRequired: false, + }, + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, }, }, } @@ -62,6 +106,71 @@ func TestExpandFeatures(t *testing.T) { } } +func TestExpandFeaturesKeyVault(t *testing.T) { + testData := []struct { + Name string + Input []interface{} + EnvVars map[string]interface{} + Expected features.UserFeatures + }{ + { + Name: "Empty Block", + Input: []interface{}{ + map[string]interface{}{ + "key_vault": []interface{}{}, + }, + }, + Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, + }, + }, + { + Name: "Purge Soft Delete On Destroy Enabled", + Input: []interface{}{ + map[string]interface{}{ + "key_vault": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": true, + }, + }, + }, + }, + Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, + }, + }, + { + Name: "Purge Soft Delete On Destroy Disabled", + Input: []interface{}{ + map[string]interface{}{ + "key_vault": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": false, + }, + }, + }, + }, + Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, + }, + }, + } + + for _, testCase := range testData { + t.Logf("[DEBUG] Test Case: %q", testCase.Name) + result := expandFeatures(testCase.Input) + if !reflect.DeepEqual(result.KeyVault, testCase.Expected.KeyVault) { + t.Fatalf("Expected %+v but got %+v", result.KeyVault, testCase.Expected.KeyVault) + } + } +} + func TestExpandFeaturesVirtualMachine(t *testing.T) { testData := []struct { Name string From 3912093b6bc472adb627d6d92905366a1bceef42 Mon Sep 17 00:00:00 2001 From: WS <20408400+WodansSon@users.noreply.github.com> Date: Thu, 6 Feb 2020 22:05:13 -0800 Subject: [PATCH 12/33] Removed sku support from datasource --- .../keyvault/data_source_key_vault.go | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/azurerm/internal/services/keyvault/data_source_key_vault.go b/azurerm/internal/services/keyvault/data_source_key_vault.go index 82ecb8e5f06e..e940f74ebc07 100644 --- a/azurerm/internal/services/keyvault/data_source_key_vault.go +++ b/azurerm/internal/services/keyvault/data_source_key_vault.go @@ -34,20 +34,6 @@ func dataSourceArmKeyVault() *schema.Resource { "location": azure.SchemaLocationForDataSource(), - // Remove in 2.0 - "sku": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - "sku_name": { Type: schema.TypeString, Computed: true, @@ -205,11 +191,6 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { d.Set("vault_uri", props.VaultURI) if sku := props.Sku; sku != nil { - // Remove in 2.0 - if err := d.Set("sku", flattenKeyVaultDataSourceSku(sku)); err != nil { - return fmt.Errorf("Error setting `sku` for KeyVault %q: %+v", *resp.Name, err) - } - if err := d.Set("sku_name", string(sku.Name)); err != nil { return fmt.Errorf("Error setting `sku_name` for KeyVault %q: %+v", *resp.Name, err) } @@ -230,15 +211,6 @@ func dataSourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { return tags.FlattenAndSet(d, resp.Tags) } -// Remove in 2.0 -func flattenKeyVaultDataSourceSku(sku *keyvault.Sku) []interface{} { - result := map[string]interface{}{ - "name": string(sku.Name), - } - - return []interface{}{result} -} - func flattenKeyVaultDataSourceNetworkAcls(input *keyvault.NetworkRuleSet) []interface{} { if input == nil { return []interface{}{} From b02dd08ce513cf50b51d1bef4f01195c5511bdf9 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 7 Feb 2020 15:51:31 -0800 Subject: [PATCH 13/33] Add code to skip purge if not soft deleted --- .../internal/services/keyvault/resource_arm_key_vault.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 2b6429646558..110bd66bebe6 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -421,9 +421,13 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { // Check to see if purge protection is enabled or not... purgeProtectionEnabled := false + softDeleteEnabled := false if ppe := read.Properties.EnablePurgeProtection; ppe != nil { purgeProtectionEnabled = *ppe } + if sde := read.Properties.EnableSoftDelete; sde != nil { + softDeleteEnabled = *sde + } // ensure we lock on the latest network names, to ensure we handle Azure's networking layer being limited to one change at a time virtualNetworkNames := make([]string, 0) @@ -459,10 +463,10 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } - if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy { + if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy && softDeleteEnabled { location := d.Get("location").(string) - // check to see if purge protection is enabled or not + // raise an error if the key vault is in the soft delete state and purge protection is enabled if purgeProtectionEnabled { delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) if err == nil { From 96f858a4757a97f3b4607f61703e315ec4dadf93 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 7 Feb 2020 16:16:13 -0800 Subject: [PATCH 14/33] Remove depricated sku from tests --- .../tests/data_source_key_vault_test.go | 6 +- .../tests/resource_arm_key_vault_key_test.go | 10 +- .../tests/resource_arm_key_vault_test.go | 113 +----------------- 3 files changed, 8 insertions(+), 121 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index 18e6526a428d..5105111f4a35 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -46,7 +46,7 @@ func TestAccDataSourceAzureRMKeyVault_basicClassic(t *testing.T) { Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttrSet(data.ResourceName, "tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "sku.0.name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "sku_name"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.tenant_id"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.object_id"), resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), @@ -71,7 +71,7 @@ func TestAccDataSourceAzureRMKeyVault_complete(t *testing.T) { Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttrSet(data.ResourceName, "tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "sku.0.name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "sku_name"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.tenant_id"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.object_id"), resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "get"), @@ -97,7 +97,7 @@ func TestAccDataSourceAzureRMKeyVault_networkAcls(t *testing.T) { Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttrSet(data.ResourceName, "tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "sku.0.name"), + resource.TestCheckResourceAttrSet(data.ResourceName, "sku_name"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.tenant_id"), resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.object_id"), resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go index b8bbcf8b3969..345f95e0b5b9 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go @@ -727,9 +727,7 @@ resource "azurerm_key_vault" "test" { resource_group_name = "${azurerm_resource_group.test.name}" tenant_id = "${data.azurerm_client_config.current.tenant_id}" - sku { - name = "premium" - } + sku_name = "premium" access_policy { tenant_id = "${data.azurerm_client_config.current.tenant_id}" @@ -782,10 +780,8 @@ resource "azurerm_key_vault" "test" { resource_group_name = "${azurerm_resource_group.test.name}" tenant_id = "${data.azurerm_client_config.current.tenant_id}" - sku { - name = "premium" - } - + sku_name = "premium" + access_policy { tenant_id = "${data.azurerm_client_config.current.tenant_id}" object_id = "${data.azurerm_client_config.current.service_principal_object_id}" diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 087882a0fe7f..2a7c4ba816fb 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -2,7 +2,6 @@ package tests import ( "fmt" - "regexp" "testing" "github.com/hashicorp/go-azure-helpers/response" @@ -92,44 +91,6 @@ func TestAccAzureRMKeyVault_basic(t *testing.T) { }) } -// Remove in 2.0 -func TestAccAzureRMKeyVault_basicNotDefined(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMKeyVaultDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMKeyVault_basicNotDefined(data), - ExpectError: regexp.MustCompile("either 'sku_name' or 'sku' must be defined in the configuration file"), - }, - }, - }) -} - -// Remove in 2.0 -func TestAccAzureRMKeyVault_basicClassic(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMKeyVaultDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAzureRMKeyVault_basicClassic(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMKeyVaultExists(data.ResourceName), - resource.TestCheckResourceAttr(data.ResourceName, "sku.0.name", "premium"), - ), - }, - data.ImportStep(), - }, - }) -} - func TestAccAzureRMKeyVault_requiresImport(t *testing.T) { if !features.ShouldResourcesBeImported() { t.Skip("Skipping since resources aren't required to be imported") @@ -488,72 +449,6 @@ resource "azurerm_key_vault" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func testAccAzureRMKeyVault_basicNotDefined(data acceptance.TestData) string { - return fmt.Sprintf(` -data "azurerm_client_config" "current" {} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_key_vault" "test" { - name = "vault%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - - access_policy { - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - object_id = "${data.azurerm_client_config.current.client_id}" - - key_permissions = [ - "create", - ] - - secret_permissions = [ - "set", - ] - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - -func testAccAzureRMKeyVault_basicClassic(data acceptance.TestData) string { - return fmt.Sprintf(` -data "azurerm_client_config" "current" {} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} - -resource "azurerm_key_vault" "test" { - name = "vault%d" - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - - sku { - name = "premium" - } - - access_policy { - tenant_id = "${data.azurerm_client_config.current.tenant_id}" - object_id = "${data.azurerm_client_config.current.client_id}" - - key_permissions = [ - "create", - ] - - secret_permissions = [ - "set", - ] - } -} -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) -} - func testAccAzureRMKeyVault_requiresImport(data acceptance.TestData) string { template := testAccAzureRMKeyVault_basic(data) return fmt.Sprintf(` @@ -778,9 +673,7 @@ resource "azurerm_key_vault" "test" { resource_group_name = "${azurerm_resource_group.test.name}" tenant_id = "${data.azurerm_client_config.current.tenant_id}" - sku { - name = "premium" - } + sku_name = "premium" enabled_for_deployment = true enabled_for_disk_encryption = true @@ -808,9 +701,7 @@ resource "azurerm_key_vault" "test" { resource_group_name = "${azurerm_resource_group.test.name}" tenant_id = "${data.azurerm_client_config.current.tenant_id}" - sku { - name = "premium" - } + sku_name = "premium" access_policy = [] From b8c1418dae86a76cd404a6c5cf1bafa0ae1ddd97 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 7 Feb 2020 16:46:57 -0800 Subject: [PATCH 15/33] Fix basic test --- .../tests/data_source_key_vault_test.go | 25 ------------------- .../tests/resource_arm_key_vault_test.go | 4 --- 2 files changed, 29 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index 5105111f4a35..cb53ca59a88b 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -33,31 +33,6 @@ func TestAccDataSourceAzureRMKeyVault_basic(t *testing.T) { }) } -func TestAccDataSourceAzureRMKeyVault_basicClassic(t *testing.T) { - data := acceptance.BuildTestData(t, "data.azurerm_key_vault", "test") - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acceptance.PreCheck(t) }, - Providers: acceptance.SupportedProviders, - CheckDestroy: testCheckAzureRMKeyVaultDestroy, - Steps: []resource.TestStep{ - { - Config: testAccDataSourceAzureRMKeyVault_basic(data), - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMKeyVaultExists(data.ResourceName), - resource.TestCheckResourceAttrSet(data.ResourceName, "tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "sku_name"), - resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.object_id"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0"), - ), - }, - }, - }) -} - func TestAccDataSourceAzureRMKeyVault_complete(t *testing.T) { data := acceptance.BuildTestData(t, "data.azurerm_key_vault", "test") diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 2a7c4ba816fb..0e5eb626b83d 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -441,10 +441,6 @@ resource "azurerm_key_vault" "test" { "set", ] } - - tags = { - environment = "Production" - } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } From abd8429b996d9ea471b8aa76e7ebb94482c32def Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Fri, 7 Feb 2020 16:56:13 -0800 Subject: [PATCH 16/33] Fix test case lint issue --- .../services/keyvault/tests/resource_arm_key_vault_key_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go index 345f95e0b5b9..154db1d273b4 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_key_test.go @@ -781,7 +781,7 @@ resource "azurerm_key_vault" "test" { tenant_id = "${data.azurerm_client_config.current.tenant_id}" sku_name = "premium" - + access_policy { tenant_id = "${data.azurerm_client_config.current.tenant_id}" object_id = "${data.azurerm_client_config.current.service_principal_object_id}" From 7f48f3a1a9f698fb8627f4ab2b55096382a485e1 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Tue, 18 Feb 2020 14:11:13 -0800 Subject: [PATCH 17/33] Update test cases --- azurerm/internal/provider/features.go | 22 +++++++++---------- .../tests/data_source_key_vault_test.go | 2 +- .../tests/resource_arm_key_vault_test.go | 8 +++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 709bd2968740..8e2670722497 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -34,18 +34,18 @@ func schemaFeatures() *schema.Schema { Required: true, }, }, + }, + }, - "key_vault": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "purge_soft_delete_on_destroy": { - Type: schema.TypeBool, - Required: true, - }, - }, + "key_vault": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "purge_soft_delete_on_destroy": { + Type: schema.TypeBool, + Required: true, }, }, }, diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index cefa1addb401..4ef5f6ba12fa 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -108,7 +108,7 @@ func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), resource.TestCheckResourceAttr(data.ResourceName, "network_acls.#", "1"), resource.TestCheckResourceAttr(data.ResourceName, "network_acls.0.default_action", "Allow"), - resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0") + resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0"), ), }, { diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 0e5eb626b83d..0699025aef01 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -847,7 +847,7 @@ func testAccAzureRMKeyVault_generateAccessPolicyConfigs(accountNum int) string { return fmt.Sprintf(` access_policy { - tenant_id = "${data.azurerm_client_config.current.tenant_id}" + tenant_id = data.azurerm_client_config.current.tenant_id object_id = "%s" key_permissions = ["get", "create", "delete", "list", "restore", "recover", "unwrapkey", "wrapkey", "purge", "encrypt", "decrypt", "sign", "verify"] @@ -878,9 +878,9 @@ resource "azurerm_resource_group" "test" { resource "azurerm_key_vault" "test" { name = "vault%d" provider = azurerm.keyVault - location = "${azurerm_resource_group.test.location}" - resource_group_name = "${azurerm_resource_group.test.name}" - tenant_id = "${data.azurerm_client_config.current.tenant_id}" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id soft_delete_enabled = true purge_protection_enabled = false From 94dfc85062c533800a1aa0dcbab799be71a480e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Tue, 18 Feb 2020 17:23:07 -0800 Subject: [PATCH 18/33] Fix test case --- .../services/keyvault/tests/data_source_key_vault_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index 4ef5f6ba12fa..c4efe633cb46 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -100,14 +100,7 @@ func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), - resource.TestCheckResourceAttrSet(data.ResourceName, "tenant_id"), resource.TestCheckResourceAttrSet(data.ResourceName, "sku_name"), - resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.tenant_id"), - resource.TestCheckResourceAttrSet(data.ResourceName, "access_policy.0.object_id"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.key_permissions.0", "create"), - resource.TestCheckResourceAttr(data.ResourceName, "access_policy.0.secret_permissions.0", "set"), - resource.TestCheckResourceAttr(data.ResourceName, "network_acls.#", "1"), - resource.TestCheckResourceAttr(data.ResourceName, "network_acls.0.default_action", "Allow"), resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0"), ), }, From 6c5e9042a75af9d4ba900db12fc5662f4ae76049 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 12:59:05 +0100 Subject: [PATCH 19/33] validate: removing the bool validators --- azurerm/helpers/validate/bool.go | 23 ------------------ azurerm/helpers/validate/bool_test.go | 35 --------------------------- 2 files changed, 58 deletions(-) delete mode 100644 azurerm/helpers/validate/bool.go delete mode 100644 azurerm/helpers/validate/bool_test.go diff --git a/azurerm/helpers/validate/bool.go b/azurerm/helpers/validate/bool.go deleted file mode 100644 index 3c7901b3636c..000000000000 --- a/azurerm/helpers/validate/bool.go +++ /dev/null @@ -1,23 +0,0 @@ -package validate - -import ( - "fmt" - - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" -) - -func BoolIsTrue() schema.SchemaValidateFunc { - return func(i interface{}, k string) (_ []string, errors []error) { - v, ok := i.(bool) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %q to be bool", k)) - return - } - - if !v { - errors = append(errors, fmt.Errorf("%q can only be set to true, if not required remove key", k)) - return - } - return - } -} diff --git a/azurerm/helpers/validate/bool_test.go b/azurerm/helpers/validate/bool_test.go deleted file mode 100644 index d378dafc41b0..000000000000 --- a/azurerm/helpers/validate/bool_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package validate - -import "testing" - -func TestBoolIsTrue(t *testing.T) { - testCases := []struct { - Value bool - ShouldHaveError bool - }{ - { - Value: true, - ShouldHaveError: false, - }, { - Value: false, - ShouldHaveError: true, - }, - } - - t.Run("TestBoolIsTrue", func(t *testing.T) { - for _, value := range testCases { - _, errors := BoolIsTrue()(value.Value, "dummy") - hasErrors := len(errors) > 0 - - if value.ShouldHaveError && !hasErrors { - t.Fatalf("Expected an error but didn't get one for %t", value.Value) - return - } - - if !value.ShouldHaveError && hasErrors { - t.Fatalf("Expected %t to return no errors, but got some %+v", value.Value, errors) - return - } - } - }) -} From 76babfac5fb496c6a11b773fd31863d5e062b33e Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 12:59:39 +0100 Subject: [PATCH 20/33] (d|r)/key_vault: moving the name validation into that package --- .../keyvault/data_source_key_vault.go | 3 +- .../keyvault/resource_arm_key_vault.go | 13 +--- .../tests/resource_arm_key_vault_test.go | 58 ----------------- .../services/keyvault/validate/name.go | 15 +++++ .../services/keyvault/validate/name_test.go | 62 +++++++++++++++++++ 5 files changed, 81 insertions(+), 70 deletions(-) create mode 100644 azurerm/internal/services/keyvault/validate/name.go create mode 100644 azurerm/internal/services/keyvault/validate/name_test.go diff --git a/azurerm/internal/services/keyvault/data_source_key_vault.go b/azurerm/internal/services/keyvault/data_source_key_vault.go index 94ae0e9d97f6..86a484a17b71 100644 --- a/azurerm/internal/services/keyvault/data_source_key_vault.go +++ b/azurerm/internal/services/keyvault/data_source_key_vault.go @@ -9,6 +9,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -26,7 +27,7 @@ func dataSourceArmKeyVault() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: ValidateKeyVaultName, + ValidateFunc: validate.KeyVaultName, }, "resource_group_name": azure.SchemaResourceGroupNameForDataSource(), diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 50fdd7d4d944..12f99756cbb3 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -4,9 +4,9 @@ import ( "fmt" "log" "net/http" - "regexp" "time" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" @@ -59,7 +59,7 @@ func resourceArmKeyVault() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: ValidateKeyVaultName, + ValidateFunc: validate.KeyVaultName, }, "location": azure.SchemaLocation(), @@ -531,15 +531,6 @@ func flattenKeyVaultNetworkAcls(input *keyvault.NetworkRuleSet) []interface{} { return []interface{}{output} } -func ValidateKeyVaultName(v interface{}, k string) (warnings []string, errors []error) { - value := v.(string) - if matched := regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`).Match([]byte(value)); !matched { - errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and must be between 3-24 chars", k)) - } - - return warnings, errors -} - func keyVaultRefreshFunc(vaultUri string) resource.StateRefreshFunc { return func() (interface{}, string, error) { log.Printf("[DEBUG] Checking to see if KeyVault %q is available..", vaultUri) diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 0699025aef01..48a3d1998e90 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -10,67 +10,9 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -func TestAccAzureRMKeyVault_name(t *testing.T) { - cases := []struct { - Input string - ExpectError bool - }{ - { - Input: "", - ExpectError: true, - }, - { - Input: "hi", - ExpectError: true, - }, - { - Input: "hello", - ExpectError: false, - }, - { - Input: "hello-world", - ExpectError: false, - }, - { - Input: "hello-world-21", - ExpectError: false, - }, - { - Input: "hello_world_21", - ExpectError: true, - }, - { - Input: "Hello-World", - ExpectError: false, - }, - { - Input: "20202020", - ExpectError: false, - }, - { - Input: "ABC123!@£", - ExpectError: true, - }, - { - Input: "abcdefghijklmnopqrstuvwxyz", - ExpectError: true, - }, - } - - for _, tc := range cases { - _, errors := keyvault.ValidateKeyVaultName(tc.Input, "") - - hasError := len(errors) > 0 - if tc.ExpectError && !hasError { - t.Fatalf("Expected the Key Vault Name to trigger a validation error for '%s'", tc.Input) - } - } -} - func TestAccAzureRMKeyVault_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") diff --git a/azurerm/internal/services/keyvault/validate/name.go b/azurerm/internal/services/keyvault/validate/name.go new file mode 100644 index 000000000000..764ceba95e23 --- /dev/null +++ b/azurerm/internal/services/keyvault/validate/name.go @@ -0,0 +1,15 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func KeyVaultName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + if matched := regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q may only contain alphanumeric characters and dashes and must be between 3-24 chars", k)) + } + + return warnings, errors +} diff --git a/azurerm/internal/services/keyvault/validate/name_test.go b/azurerm/internal/services/keyvault/validate/name_test.go new file mode 100644 index 000000000000..c574c749f541 --- /dev/null +++ b/azurerm/internal/services/keyvault/validate/name_test.go @@ -0,0 +1,62 @@ +package validate + +import ( + "testing" +) + +func TestValidateKeyVaultName(t *testing.T) { + cases := []struct { + Input string + ExpectError bool + }{ + { + Input: "", + ExpectError: true, + }, + { + Input: "hi", + ExpectError: true, + }, + { + Input: "hello", + ExpectError: false, + }, + { + Input: "hello-world", + ExpectError: false, + }, + { + Input: "hello-world-21", + ExpectError: false, + }, + { + Input: "hello_world_21", + ExpectError: true, + }, + { + Input: "Hello-World", + ExpectError: false, + }, + { + Input: "20202020", + ExpectError: false, + }, + { + Input: "ABC123!@£", + ExpectError: true, + }, + { + Input: "abcdefghijklmnopqrstuvwxyz", + ExpectError: true, + }, + } + + for _, tc := range cases { + _, errors := KeyVaultName(tc.Input, "") + + hasError := len(errors) > 0 + if tc.ExpectError && !hasError { + t.Fatalf("Expected the Key Vault Name to trigger a validation error for '%s'", tc.Input) + } + } +} From d38b7437dd080b7985f31b303c53f6c15d4570aa Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 15:57:40 +0100 Subject: [PATCH 21/33] r/key_vault: moving the migration code closer to the package --- .../services/keyvault/resource_arm_key_vault_migration.go | 2 +- .../{tests => }/resource_arm_key_vault_migration_test.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) rename azurerm/internal/services/keyvault/{tests => }/resource_arm_key_vault_migration_test.go (98%) diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault_migration.go b/azurerm/internal/services/keyvault/resource_arm_key_vault_migration.go index 5e5930a0d2a4..f3dff4890ebe 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault_migration.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault_migration.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/terraform" ) -func ResourceAzureRMKeyVaultMigrateState(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { +func resourceAzureRMKeyVaultMigrateState(v int, is *terraform.InstanceState, _ interface{}) (*terraform.InstanceState, error) { switch v { case 0: log.Println("[INFO] Found AzureRM Key Vault State v0; migrating to v1") diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_migration_test.go b/azurerm/internal/services/keyvault/resource_arm_key_vault_migration_test.go similarity index 98% rename from azurerm/internal/services/keyvault/tests/resource_arm_key_vault_migration_test.go rename to azurerm/internal/services/keyvault/resource_arm_key_vault_migration_test.go index 97a6998d2bf2..34b92ff2b0b8 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_migration_test.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault_migration_test.go @@ -1,14 +1,13 @@ -package tests +package keyvault import ( "reflect" "testing" "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault" ) -func TestAzureRMKeyVaultMigrateState(t *testing.T) { +func TestKeyVaultMigrateState(t *testing.T) { cases := map[string]struct { StateVersion int ID string @@ -366,7 +365,7 @@ func TestAzureRMKeyVaultMigrateState(t *testing.T) { ID: tc.ID, Attributes: tc.Attributes, } - is, err := keyvault.ResourceAzureRMKeyVaultMigrateState(tc.StateVersion, is, tc.Meta) + is, err := resourceAzureRMKeyVaultMigrateState(tc.StateVersion, is, tc.Meta) if err != nil { t.Fatalf("bad: %q, err: %+v", tn, err) From 586901dc78b39e0b23d31e8da8136a79e4dbeae0 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 20:09:51 +0100 Subject: [PATCH 22/33] r/key_vault: handling conditionally updating the key vault resource --- azurerm/helpers/azure/key_vault.go | 45 -- azurerm/internal/features/user_flags.go | 2 +- azurerm/internal/provider/features.go | 26 +- azurerm/internal/provider/features_test.go | 38 +- .../keyvault/resource_arm_key_vault.go | 445 ++++++++++++------ .../tests/data_source_key_vault_test.go | 8 +- .../tests/resource_arm_key_vault_test.go | 306 +++++++++++- 7 files changed, 619 insertions(+), 251 deletions(-) diff --git a/azurerm/helpers/azure/key_vault.go b/azurerm/helpers/azure/key_vault.go index f75368e5d237..7957380bb594 100644 --- a/azurerm/helpers/azure/key_vault.go +++ b/azurerm/helpers/azure/key_vault.go @@ -3,10 +3,8 @@ package azure import ( "context" "fmt" - "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -120,46 +118,3 @@ func KeyVaultExists(ctx context.Context, client *keyvault.VaultsClient, keyVault return true, nil } - -func KeyVaultCustomizeDiff(d *schema.ResourceDiff, _ interface{}) error { - if d.HasChange("soft_delete_enabled") { - if old, new := d.GetChange("soft_delete_enabled"); old.(bool) && !new.(bool) { - return fmt.Errorf("the property 'soft_delete_enabled' cannot be set to false, enabling the soft delete for a vault is an irreversible action") - } - } - - if d.HasChange("purge_protection_enabled") { - if old, new := d.GetChange("purge_protection_enabled"); old.(bool) && !new.(bool) { - return fmt.Errorf("the property 'purge_protection_enabled' cannot be set to false, enabling the purge protection for a vault is an irreversible action") - } - } - - return nil -} - -func KeyVaultGetSoftDeletedState(ctx context.Context, client *keyvault.VaultsClient, name string, location string) (deleteDate interface{}, purgeDate interface{}, err error) { - softDel, err := client.GetDeleted(ctx, name, location) - if err != nil { - return nil, nil, fmt.Errorf("unable to get soft delete state information: %+v", err) - } - - // the logic is this way because the GetDeleted call will return an existing key vault - // that is not soft deleted, but the Deleted Vault properties will be nil - if props := softDel.Properties; props != nil { - var delDate interface{} - var purgeDate interface{} - - if dd := props.DeletionDate; dd != nil { - delDate = (*dd).Format(time.RFC3339) - } - if pg := props.ScheduledPurgeDate; pg != nil { - purgeDate = (*pg).Format(time.RFC3339) - } - if delDate != nil && purgeDate != nil { - return delDate, purgeDate, nil - } - } - - // this means we found an existing key vault that is not soft deleted - return nil, nil, nil -} diff --git a/azurerm/internal/features/user_flags.go b/azurerm/internal/features/user_flags.go index 834b6bc903c3..93407b225c2e 100644 --- a/azurerm/internal/features/user_flags.go +++ b/azurerm/internal/features/user_flags.go @@ -15,5 +15,5 @@ type VirtualMachineScaleSetFeatures struct { } type KeyVaultFeatures struct { - PurgeSoftDeleteOnDestroy bool + RecoverSoftDeletedKeyVaults bool } diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 8e2670722497..c0ea99f52950 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -8,6 +8,8 @@ import ( ) func schemaFeatures() *schema.Schema { + // NOTE: if there's only one nested field these want to be Required (since there's no point + // specifying the block otherwise) - however for 2+ they should be optional features := map[string]*schema.Schema{ "virtual_machine": { Type: schema.TypeList, @@ -43,7 +45,7 @@ func schemaFeatures() *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "purge_soft_delete_on_destroy": { + "recover_soft_deleted_key_vaults": { Type: schema.TypeBool, Required: true, }, @@ -85,7 +87,7 @@ func expandFeatures(input []interface{}) features.UserFeatures { RollInstancesWhenRequired: true, }, KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: true, + RecoverSoftDeletedKeyVaults: true, }, } @@ -95,6 +97,16 @@ func expandFeatures(input []interface{}) features.UserFeatures { val := input[0].(map[string]interface{}) + if raw, ok := val["key_vault"]; ok { + items := raw.([]interface{}) + if len(items) > 0 { + keyVaultRaw := items[0].(map[string]interface{}) + if v, ok := keyVaultRaw["recover_soft_deleted_key_vaults"]; ok { + features.KeyVault.RecoverSoftDeletedKeyVaults = v.(bool) + } + } + } + if raw, ok := val["virtual_machine"]; ok { items := raw.([]interface{}) if len(items) > 0 { @@ -115,15 +127,5 @@ func expandFeatures(input []interface{}) features.UserFeatures { } } - if raw, ok := val["key_vault"]; ok { - items := raw.([]interface{}) - if len(items) > 0 { - keyVaultRaw := items[0].(map[string]interface{}) - if v, ok := keyVaultRaw["purge_soft_delete_on_destroy"]; ok { - features.KeyVault.PurgeSoftDeleteOnDestroy = v.(bool) - } - } - } - return features } diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index 41041e6d1e34..7d24f1bf07d3 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -18,15 +18,15 @@ func TestExpandFeatures(t *testing.T) { Name: "Empty Block", Input: []interface{}{}, Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + RecoverSoftDeletedKeyVaults: true, + }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, }, - KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: true, - }, }, }, { @@ -45,21 +45,22 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": true, + "purge_soft_delete_on_destroy": true, + "recover_soft_deleted_key_vaults": true, }, }, }, }, Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + RecoverSoftDeletedKeyVaults: true, + }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: true, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: true, }, - KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: true, - }, }, }, { @@ -78,21 +79,22 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": false, + "purge_soft_delete_on_destroy": false, + "recover_soft_deleted_key_vaults": false, }, }, }, }, Expected: features.UserFeatures{ + KeyVault: features.KeyVaultFeatures{ + RecoverSoftDeletedKeyVaults: false, + }, VirtualMachine: features.VirtualMachineFeatures{ DeleteOSDiskOnDeletion: false, }, VirtualMachineScaleSet: features.VirtualMachineScaleSetFeatures{ RollInstancesWhenRequired: false, }, - KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: false, - }, }, }, } @@ -122,41 +124,41 @@ func TestExpandFeaturesKeyVault(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: true, + RecoverSoftDeletedKeyVaults: true, }, }, }, { - Name: "Purge Soft Delete On Destroy Enabled", + Name: "Recover Soft Deleted Key Vaults Enabled", Input: []interface{}{ map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": true, + "recover_soft_deleted_key_vaults": true, }, }, }, }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: true, + RecoverSoftDeletedKeyVaults: true, }, }, }, { - Name: "Purge Soft Delete On Destroy Disabled", + Name: "Recover Soft Deleted Key Vaults Disabled", Input: []interface{}{ map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": false, + "recover_soft_deleted_key_vaults": false, }, }, }, }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ - PurgeSoftDeleteOnDestroy: false, + RecoverSoftDeletedKeyVaults: false, }, }, }, diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 12f99756cbb3..2a878c12f6e8 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -19,7 +19,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -35,16 +34,16 @@ var keyVaultResourceName = "azurerm_key_vault" func resourceArmKeyVault() *schema.Resource { return &schema.Resource{ - Create: resourceArmKeyVaultCreateUpdate, + Create: resourceArmKeyVaultCreate, Read: resourceArmKeyVaultRead, - Update: resourceArmKeyVaultCreateUpdate, + Update: resourceArmKeyVaultUpdate, Delete: resourceArmKeyVaultDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, - MigrateState: ResourceAzureRMKeyVaultMigrateState, + MigrateState: resourceAzureRMKeyVaultMigrateState, SchemaVersion: 1, Timeouts: &schema.ResourceTimeout{ @@ -75,11 +74,6 @@ func resourceArmKeyVault() *schema.Resource { }, false), }, - "vault_uri": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { Type: schema.TypeString, Required: true, @@ -182,68 +176,76 @@ func resourceArmKeyVault() *schema.Resource { }, "tags": tags.Schema(), - }, - CustomizeDiff: azure.KeyVaultCustomizeDiff, + // Computed + "vault_uri": { + Type: schema.TypeString, + Computed: true, + }, + }, } } -func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceArmKeyVaultCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).KeyVault.VaultsClient - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - log.Printf("[INFO] preparing arguments for Azure ARM KeyVault creation.") - name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - purgeProtectionEnabled := d.Get("purge_protection_enabled").(bool) + location := azure.NormalizeLocation(d.Get("location").(string)) - if features.ShouldResourcesBeImported() && d.IsNewResource() { - existing, err := client.Get(ctx, resourceGroup, name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("Error checking for presence of existing Key Vault %q (Resource Group %q): %s", name, resourceGroup, err) - } - } + // Locking this resource so we don't make modifications to it at the same time if there is a + // key vault access policy trying to update it as well + locks.ByName(name, keyVaultResourceName) + defer locks.UnlockByName(name, keyVaultResourceName) - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_key_vault", *existing.ID) + // check for the presence of an existing, live one which should be imported into the state + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Key Vault %q (Resource Group %q): %s", name, resourceGroup, err) } } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_key_vault", *existing.ID) + } + // before creating check to see if the key vault exists in the soft delete state - location := azure.NormalizeLocation(d.Get("location").(string)) - delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) - if err == nil { - if delDate != nil && purgeDate != nil { - return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists in the soft delete state. The key vault was soft deleted on %s and is scheduled to be purged on %s, please use the Azure CLI tool to recover this Key Vault", name, resourceGroup, delDate.(string), purgeDate.(string)) + softDeletedKeyVault, err := client.GetDeleted(ctx, name, location) + if err != nil { + if !utils.ResponseWasNotFound(softDeletedKeyVault.Response) { + return fmt.Errorf("Error checking for the presence of an existing Soft-Deleted Key Vault %q (Location %q): %+v", name, location, err) } - return fmt.Errorf("unable to create Key Vault %q (Resource Group %q) becauese it already exists", name, resourceGroup) } - // check to see if they enabled purge protection and purge on destroy - if purgeProtectionEnabled && meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy { - return fmt.Errorf("conflicting properties 'purge_protection_enabled' and 'purge_soft_delete_on_destroy' are both enabled for Key Vault %q (Resource Group %q). Please disable one of these paroperties and re-apply", name, resourceGroup) + // if so, does the user want us to recover it? + recoverSoftDeletedKeyVault := false + if !utils.ResponseWasNotFound(softDeletedKeyVault.Response) { + if !meta.(*clients.Client).Features.KeyVault.RecoverSoftDeletedKeyVaults { + // this exists but the users opted out so they must import this it out-of-band + return fmt.Errorf(optedOutOfRecoveringSoftDeletedKeyVaultErrorFmt(name, location)) + } + + recoverSoftDeletedKeyVault = true } tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) enabledForDeployment := d.Get("enabled_for_deployment").(bool) enabledForDiskEncryption := d.Get("enabled_for_disk_encryption").(bool) enabledForTemplateDeployment := d.Get("enabled_for_template_deployment").(bool) - softDeleteEnabled := d.Get("soft_delete_enabled").(bool) - t := d.Get("tags").(map[string]interface{}) - networkAclsRaw := d.Get("network_acls").([]interface{}) - networkAcls, subnetIds := expandKeyVaultNetworkAcls(networkAclsRaw) - policies := d.Get("access_policy").([]interface{}) accessPolicies, err := azure.ExpandKeyVaultAccessPolicies(policies) if err != nil { - return fmt.Errorf("Error expanding `access_policy`: %+v", policies) + return fmt.Errorf("Error expanding `access_policy`: %+v", err) } + networkAclsRaw := d.Get("network_acls").([]interface{}) + networkAcls, subnetIds := expandKeyVaultNetworkAcls(networkAclsRaw) + sku := keyvault.Sku{ Family: &armKeyVaultSkuFamily, Name: keyvault.SkuName(d.Get("sku_name").(string)), @@ -264,17 +266,16 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e } // This settings can only be set if it is true, if set when value is false API returns errors - if softDeleteEnabled { - parameters.Properties.EnableSoftDelete = &softDeleteEnabled + if softDeleteEnabled := d.Get("soft_delete_enabled").(bool); softDeleteEnabled { + parameters.Properties.EnableSoftDelete = utils.Bool(softDeleteEnabled) } - if purgeProtectionEnabled { - parameters.Properties.EnablePurgeProtection = &purgeProtectionEnabled + if purgeProtectionEnabled := d.Get("purge_protection_enabled").(bool); purgeProtectionEnabled { + parameters.Properties.EnablePurgeProtection = utils.Bool(purgeProtectionEnabled) } - // Locking this resource so we don't make modifications to it at the same time if there is a - // key vault access policy trying to update it as well - locks.ByName(name, keyVaultResourceName) - defer locks.UnlockByName(name, keyVaultResourceName) + if recoverSoftDeletedKeyVault { + parameters.Properties.CreateMode = keyvault.CreateModeRecover + } // also lock on the Virtual Network ID's since modifications in the networking stack are exclusive virtualNetworkNames := make([]string, 0) @@ -293,8 +294,8 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) - if _, err = client.CreateOrUpdate(ctx, resourceGroup, name, parameters); err != nil { - return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, parameters); err != nil { + return fmt.Errorf("Error creating Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) } read, err := client.Get(ctx, resourceGroup, name) @@ -307,23 +308,21 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e d.SetId(*read.ID) - if d.IsNewResource() { - if props := read.Properties; props != nil { - if vault := props.VaultURI; vault != nil { - log.Printf("[DEBUG] Waiting for Key Vault %q (Resource Group %q) to become available", name, resourceGroup) - stateConf := &resource.StateChangeConf{ - Pending: []string{"pending"}, - Target: []string{"available"}, - Refresh: keyVaultRefreshFunc(*vault), - Delay: 30 * time.Second, - PollInterval: 10 * time.Second, - ContinuousTargetOccurence: 10, - Timeout: d.Timeout(schema.TimeoutCreate), - } + if props := read.Properties; props != nil { + if vault := props.VaultURI; vault != nil { + log.Printf("[DEBUG] Waiting for Key Vault %q (Resource Group %q) to become available", name, resourceGroup) + stateConf := &resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"available"}, + Refresh: keyVaultRefreshFunc(*vault), + Delay: 30 * time.Second, + PollInterval: 10 * time.Second, + ContinuousTargetOccurence: 10, + Timeout: d.Timeout(schema.TimeoutCreate), + } - if _, err := stateConf.WaitForState(); err != nil { - return fmt.Errorf("Error waiting for Key Vault %q (Resource Group %q) to become available: %s", name, resourceGroup, err) - } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Key Vault %q (Resource Group %q) to become available: %s", name, resourceGroup, err) } } } @@ -331,6 +330,179 @@ func resourceArmKeyVaultCreateUpdate(d *schema.ResourceData, meta interface{}) e return resourceArmKeyVaultRead(d, meta) } +func resourceArmKeyVaultUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).KeyVault.VaultsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["vaults"] + + // Locking this resource so we don't make modifications to it at the same time if there is a + // key vault access policy trying to update it as well + locks.ByName(name, keyVaultResourceName) + defer locks.UnlockByName(name, keyVaultResourceName) + + d.Partial(true) + + // first pull the existing key vault since we need to lock on several bits of it's information + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if existing.Properties == nil { + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): `properties` was nil", name, resourceGroup) + } + + update := keyvault.VaultPatchParameters{} + + if d.HasChange("access_policy") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + policiesRaw := d.Get("access_policy").([]interface{}) + accessPolicies, err := azure.ExpandKeyVaultAccessPolicies(policiesRaw) + if err != nil { + return fmt.Errorf("Error expanding `access_policy`: %+v", err) + } + update.Properties.AccessPolicies = accessPolicies + } + + if d.HasChange("enabled_for_deployment") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + update.Properties.EnabledForDeployment = utils.Bool(d.Get("enabled_for_deployment").(bool)) + } + + if d.HasChange("enabled_for_disk_encryption") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + update.Properties.EnabledForDiskEncryption = utils.Bool(d.Get("enabled_for_disk_encryption").(bool)) + } + + if d.HasChange("enabled_for_template_deployment") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + update.Properties.EnabledForTemplateDeployment = utils.Bool(d.Get("enabled_for_template_deployment").(bool)) + } + + if d.HasChange("network_acls") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + networkAclsRaw := d.Get("network_acls").([]interface{}) + networkAcls, subnetIds := expandKeyVaultNetworkAcls(networkAclsRaw) + + // also lock on the Virtual Network ID's since modifications in the networking stack are exclusive + virtualNetworkNames := make([]string, 0) + for _, v := range subnetIds { + id, err2 := azure.ParseAzureResourceID(v) + if err2 != nil { + return err2 + } + + virtualNetworkName := id.Path["virtualNetworks"] + if !azure.SliceContainsValue(virtualNetworkNames, virtualNetworkName) { + virtualNetworkNames = append(virtualNetworkNames, virtualNetworkName) + } + } + + locks.MultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) + defer locks.UnlockMultipleByName(&virtualNetworkNames, network.VirtualNetworkResourceName) + + update.Properties.NetworkAcls = networkAcls + } + + if d.HasChange("purge_protection_enabled") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + newValue := d.Get("purge_protection_enabled").(bool) + + // existing.Properties guaranteed non-nil above + oldValue := false + if existing.Properties.EnablePurgeProtection != nil { + oldValue = *existing.Properties.EnablePurgeProtection + } + + // whilst this should have got caught in the customizeDiff this won't work if that fields interpolated + // hence the double-checking here + if oldValue && !newValue { + return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): once Purge Protection has been Enabled it's not possible to disable it", name, resourceGroup) + } + + update.Properties.EnablePurgeProtection = utils.Bool(newValue) + } + + if d.HasChange("sku_name") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + update.Properties.Sku = &keyvault.Sku{ + Family: &armKeyVaultSkuFamily, + Name: keyvault.SkuName(d.Get("sku_name").(string)), + } + } + + if d.HasChange("soft_delete_enabled") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + newValue := d.Get("soft_delete_enabled").(bool) + + // existing.Properties guaranteed non-nil above + oldValue := false + if existing.Properties.EnableSoftDelete != nil { + oldValue = *existing.Properties.EnableSoftDelete + } + + // whilst this should have got caught in the customizeDiff this won't work if that fields interpolated + // hence the double-checking here + if oldValue && !newValue { + return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): once Soft Delete has been Enabled it's not possible to disable it", name, resourceGroup) + } + + update.Properties.EnableSoftDelete = utils.Bool(newValue) + } + + if d.HasChange("tenant_id") { + if update.Properties == nil { + update.Properties = &keyvault.VaultPatchProperties{} + } + + tenantUUID := uuid.FromStringOrNil(d.Get("tenant_id").(string)) + update.Properties.TenantID = &tenantUUID + } + + if d.HasChange("tags") { + t := d.Get("tags").(map[string]interface{}) + update.Tags = tags.Expand(t) + } + + if _, err := client.Update(ctx, resourceGroup, name, update); err != nil { + return fmt.Errorf("Error updating Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Partial(false) + + return resourceArmKeyVaultRead(d, meta) +} + func resourceArmKeyVaultRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).KeyVault.VaultsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) @@ -413,14 +585,8 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) } - // Check to see if purge protection is enabled or not... - purgeProtectionEnabled := false - softDeleteEnabled := false - if ppe := read.Properties.EnablePurgeProtection; ppe != nil { - purgeProtectionEnabled = *ppe - } - if sde := read.Properties.EnableSoftDelete; sde != nil { - softDeleteEnabled = *sde + if read.Properties == nil { + return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): `properties` was nil", name, resourceGroup) } // ensure we lock on the latest network names, to ensure we handle Azure's networking layer being limited to one change at a time @@ -457,80 +623,9 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } - if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy && softDeleteEnabled { - location := d.Get("location").(string) - - // raise an error if the key vault is in the soft delete state and purge protection is enabled - if purgeProtectionEnabled { - delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) - if err == nil { - if delDate != nil && purgeDate != nil { - return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate.(string), purgeDate.(string)) - } - return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled", name, resourceGroup) - } - } - - log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) - future, err := client.PurgeDeleted(ctx, name, location) - if err != nil { - return err - } - - log.Printf("[DEBUG] Waiting for purge of KeyVault %s", name) - err = future.WaitForCompletionRef(ctx, client.Client) - if err != nil { - return fmt.Errorf("Error purging Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) - } - } - return nil } -func flattenKeyVaultNetworkAcls(input *keyvault.NetworkRuleSet) []interface{} { - if input == nil { - return []interface{}{ - map[string]interface{}{ - "bypass": string(keyvault.AzureServices), - "default_action": string(keyvault.Allow), - "ip_rules": schema.NewSet(schema.HashString, []interface{}{}), - "virtual_network_subnet_ids": schema.NewSet(schema.HashString, []interface{}{}), - }, - } - } - - output := make(map[string]interface{}) - - output["bypass"] = string(input.Bypass) - output["default_action"] = string(input.DefaultAction) - - ipRules := make([]interface{}, 0) - if input.IPRules != nil { - for _, v := range *input.IPRules { - if v.Value == nil { - continue - } - - ipRules = append(ipRules, *v.Value) - } - } - output["ip_rules"] = schema.NewSet(schema.HashString, ipRules) - - virtualNetworkRules := make([]interface{}, 0) - if input.VirtualNetworkRules != nil { - for _, v := range *input.VirtualNetworkRules { - if v.ID == nil { - continue - } - - virtualNetworkRules = append(virtualNetworkRules, *v.ID) - } - } - output["virtual_network_subnet_ids"] = schema.NewSet(schema.HashString, virtualNetworkRules) - - return []interface{}{output} -} - func keyVaultRefreshFunc(vaultUri string) resource.StateRefreshFunc { return func() (interface{}, string, error) { log.Printf("[DEBUG] Checking to see if KeyVault %q is available..", vaultUri) @@ -594,3 +689,63 @@ func expandKeyVaultNetworkAcls(input []interface{}) (*keyvault.NetworkRuleSet, [ } return &ruleSet, subnetIds } + +func flattenKeyVaultNetworkAcls(input *keyvault.NetworkRuleSet) []interface{} { + if input == nil { + return []interface{}{ + map[string]interface{}{ + "bypass": string(keyvault.AzureServices), + "default_action": string(keyvault.Allow), + "ip_rules": schema.NewSet(schema.HashString, []interface{}{}), + "virtual_network_subnet_ids": schema.NewSet(schema.HashString, []interface{}{}), + }, + } + } + + output := make(map[string]interface{}) + + output["bypass"] = string(input.Bypass) + output["default_action"] = string(input.DefaultAction) + + ipRules := make([]interface{}, 0) + if input.IPRules != nil { + for _, v := range *input.IPRules { + if v.Value == nil { + continue + } + + ipRules = append(ipRules, *v.Value) + } + } + output["ip_rules"] = schema.NewSet(schema.HashString, ipRules) + + virtualNetworkRules := make([]interface{}, 0) + if input.VirtualNetworkRules != nil { + for _, v := range *input.VirtualNetworkRules { + if v.ID == nil { + continue + } + + virtualNetworkRules = append(virtualNetworkRules, *v.ID) + } + } + output["virtual_network_subnet_ids"] = schema.NewSet(schema.HashString, virtualNetworkRules) + + return []interface{}{output} +} + +func optedOutOfRecoveringSoftDeletedKeyVaultErrorFmt(name, location string) string { + return fmt.Sprintf(` +An existing soft-deleted Key Vault exists with the Name %q in the location %q, however +automatically recovering this KeyVault has been disabled via the "features" block. + +Terraform can automatically recover the soft-deleted Key Vault when this behaviour is +enabled within the "features" block (located within the "provider" block) - more +information can be found here: + +https://www.terraform.io/docs/providers/azurerm/index.html#features + +Alternatively you can manually recover this (e.g. using the Azure CLI) and then import +this into Terraform via "terraform import", or pick a different name/location. +`, name, location) +} diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index c4efe633cb46..e96326fbc1ad 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -95,7 +95,7 @@ func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { CheckDestroy: testCheckAzureRMKeyVaultDestroy, Steps: []resource.TestStep{ { - Config: testAccDataSourceAzureRMKeyVault_enable_soft_delete(data), + Config: testAccDataSourceAzureRMKeyVault_enableSoftDelete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), @@ -105,7 +105,7 @@ func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { ), }, { - Config: testAccAzureRMKeyVault_softDeletePurge(data), + Config: testAccAzureRMKeyVault_softDeleteAbsent(data), }, }, }) @@ -147,8 +147,8 @@ data "azurerm_key_vault" "test" { `, r) } -func testAccDataSourceAzureRMKeyVault_enable_soft_delete(data acceptance.TestData) string { - r := testAccAzureRMKeyVault_softDelete(data) +func testAccDataSourceAzureRMKeyVault_enableSoftDelete(data acceptance.TestData) string { + r := testAccAzureRMKeyVault_softDelete(data, true) return fmt.Sprintf(` %s diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 48a3d1998e90..d357df9493c3 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -2,6 +2,7 @@ package tests import ( "fmt" + "regexp" "testing" "github.com/hashicorp/go-azure-helpers/response" @@ -234,7 +235,7 @@ func TestAccAzureRMKeyVault_justCert(t *testing.T) { }) } -func TestAccAzureRMKeyVault_softDelete(t *testing.T) { +func TestAccAzureRMKeyVault_softDeleteEnabled(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") resource.ParallelTest(t, resource.TestCase{ @@ -243,26 +244,233 @@ func TestAccAzureRMKeyVault_softDelete(t *testing.T) { CheckDestroy: testCheckAzureRMKeyVaultDestroy, Steps: []resource.TestStep{ { - Config: testAccAzureRMKeyVault_softDelete(data), + Config: testAccAzureRMKeyVault_softDelete(data, true), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), ), }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_softDeleteViaUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_softDelete(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "false"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMKeyVault_softDelete(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_softDeleteAttemptToDisable(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_softDelete(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMKeyVault_softDelete(data, false), + ExpectError: regexp.MustCompile("once Soft Delete has been Enabled it's not possible to disable it"), + }, + }, + }) +} + +func TestAccAzureRMKeyVault_softDeleteRecovery(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ { - Config: testAccAzureRMKeyVault_softDeletePurge(data), + // create it regularly + Config: testAccAzureRMKeyVault_softDelete(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + { + // delete the key vault + Config: testAccAzureRMKeyVault_softDeleteAbsent(data), + }, + { + // attempting to re-create it requires recovery, which is enabled by default + Config: testAccAzureRMKeyVault_softDelete(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_softDeleteRecoveryDisabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + // create it regularly + Config: testAccAzureRMKeyVault_softDelete(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + ), + }, + data.ImportStep(), + { + // delete the key vault + Config: testAccAzureRMKeyVault_softDeleteAbsent(data), + }, + { + // attempting to re-create it requires recovery, which is enabled by default + Config: testAccAzureRMKeyVault_softDeleteRecoveryDisabled(data), + ExpectError: regexp.MustCompile("An existing soft-deleted Key Vault exists with the Name"), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_purgeProtectionEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_purgeProtection(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "false"), + ), }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_purgeProtectionAndSoftDeleteEnabled(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ { - Config: testAccAzureRMKeyVault_softDelete(data), + Config: testAccAzureRMKeyVault_purgeProtectionAndSoftDelete(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "true"), resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_purgeProtectionViaUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_purgeProtection(data, false), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "false"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "false"), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMKeyVault_purgeProtection(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "false"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMKeyVault_purgeProtectionAttemptToDisable(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMKeyVaultDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMKeyVault_purgeProtection(data, true), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultExists(data.ResourceName), + resource.TestCheckResourceAttr(data.ResourceName, "purge_protection_enabled", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "false"), ), }, + data.ImportStep(), { - Config: testAccAzureRMKeyVault_softDeletePurge(data), + Config: testAccAzureRMKeyVault_purgeProtection(data, false), + ExpectError: regexp.MustCompile("TODO"), // TODO: correct error message }, }, }) @@ -798,20 +1006,30 @@ access_policy { `, oid) } -func testAccAzureRMKeyVault_softDelete(data acceptance.TestData) string { +func testAccAzureRMKeyVault_purgeProtection(data acceptance.TestData, enabled bool) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} -provider "azurerm" { - alias = "keyVault" +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} - features { - key_vault { - purge_soft_delete_on_destroy = true - } - } +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + purge_protection_enabled = %t +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, enabled) } +func testAccAzureRMKeyVault_softDelete(data acceptance.TestData, enabled bool) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "%s" @@ -819,35 +1037,71 @@ resource "azurerm_resource_group" "test" { resource "azurerm_key_vault" "test" { name = "vault%d" - provider = azurerm.keyVault location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name tenant_id = data.azurerm_client_config.current.tenant_id - soft_delete_enabled = true - purge_protection_enabled = false - - sku_name = "premium" + sku_name = "premium" + soft_delete_enabled = %t } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, enabled) } -func testAccAzureRMKeyVault_softDeletePurge(data acceptance.TestData) string { +func testAccAzureRMKeyVault_softDeleteAbsent(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} -provider "azurerm" { - alias = "keyVault" +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} +func testAccAzureRMKeyVault_softDeleteRecoveryDisabled(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { features { - key_vault { - purge_soft_delete_on_destroy = true - } + key_vault { + recover_soft_deleted_key_vaults = false + } } } +data "azurerm_client_config" "current" {} + resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "%s" } -`, data.RandomInteger, data.Locations.Primary) + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_enabled = true +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func testAccAzureRMKeyVault_purgeProtectionAndSoftDelete(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_enabled = true + purge_protection_enabled = true +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } From 872de6b0f28797248c59ab70597f56beddac2970 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 20:24:50 +0100 Subject: [PATCH 23/33] r/key_vault: updating the docs --- website/docs/r/key_vault.html.markdown | 33 +++++++++++--------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index e929aea49f18..918591ba6da2 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -10,21 +10,15 @@ description: |- Manages a Key Vault. -~> **NOTE:** It's possible to define Key Vault Access Policies both within [the `azurerm_key_vault` resource](key_vault.html) via the `access_policy` block and by using [the `azurerm_key_vault_access_policy` resource](key_vault_access_policy.html). However it's not possible to use both methods to manage Access Policies within a KeyVault, since there'll be conflicts. +## Disclaimers -## Example Usage +~> **Note:** It's possible to define Key Vault Access Policies both within [the `azurerm_key_vault` resource](key_vault.html) via the `access_policy` block and by using [the `azurerm_key_vault_access_policy` resource](key_vault_access_policy.html). However it's not possible to use both methods to manage Access Policies within a KeyVault, since there'll be conflicts. -```hcl -provider "azurerm" { - alias = "keyVault" +~> **Note:** Terraform will automatically recover a soft-deleted Key Vault during Creation if one is found - you can opt out of this using the `features` block within the Provider block. - features { - key_vault { - purge_soft_delete_on_destroy = true - } - } -} +## Example Usage +```hcl resource "azurerm_resource_group" "example" { name = "resourceGroup1" location = "West US" @@ -32,7 +26,6 @@ resource "azurerm_resource_group" "example" { resource "azurerm_key_vault" "example" { name = "testvault" - provider = azurerm.keyVault location = azurerm_resource_group.example.location resource_group_name = azurerm_resource_group.example.name enabled_for_disk_encryption = true @@ -84,6 +77,8 @@ The following arguments are supported: * `tenant_id` - (Required) The Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. +--- + * `access_policy` - (Optional) [A list](/docs/configuration/attr-as-blocks.html) of up to 16 objects describing access policies, as described below. ~> **NOTE:** It's possible to define Key Vault Access Policies both within [the `azurerm_key_vault` resource](key_vault.html) via the `access_policy` block and by using [the `azurerm_key_vault_access_policy` resource](key_vault_access_policy.html). However it's not possible to use both methods to manage Access Policies within a KeyVault, since there'll be conflicts. @@ -94,15 +89,17 @@ The following arguments are supported: * `enabled_for_template_deployment` - (Optional) Boolean flag to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault. Defaults to `false`. -* `soft_delete_enabled` - (Optional) Should Soft Delete be enabled for this Key Vault? +* `network_acls` - (Optional) A `network_acls` block as defined below. + +* `purge_protection_enabled` - (Optional) Is Purge Protection enabled for this Key Vault? Defaults to `false`. --> **NOTE:** Once `soft_delete_enabled` has been enabled it is an **irreversible** action. If you want to destroy this key vault before the 90 day purge policy expires you must set the `purge_soft_delete_on_destroy` to **true** in the `key_vault` section of the azure provider `features` block. +!> **Note:** Once Purge Protection has been Enabled it's not possible to Disable it. Support for [disabling purge protection is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075). -* `purge_protection_enabled` - (Optional) Is Purge Protection enabled for this Key Vault? +* `soft_delete_enabled` - (Optional) Should Soft Delete be enabled for this Key Vault? Defaults to `false`. --> **NOTE:** Once `purge_protection_enabled` has been enabled it is an **irreversible** action. - [support for this is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075) +!> **Note:** Once Soft Delete has been Enabled it's not possible to Disable it. -* `network_acls` - (Optional) A `network_acls` block as defined below. +~> **Note:** Terraform will check when creating a Key Vault for a previous soft-deleted Key Vault and recover it if one exists. You can configure this behaviour using the `features` block within the `provider` block. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -110,8 +107,6 @@ The following arguments are supported: A `access_policy` block supports the following: -Elements of `access_policy` support: - * `tenant_id` - (Required) The Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Must match the `tenant_id` used above. * `object_id` - (Required) The object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault. The object ID must be unique for the list of access policies. From d2cd2486124bb8a730460c25e0c8b6a6857d0315 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 20 Feb 2020 20:31:22 +0100 Subject: [PATCH 24/33] minor: committing some lingering test files --- azurerm/internal/provider/features_test.go | 2 -- website/docs/index.html.markdown | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index 7d24f1bf07d3..6cab40a7f8ec 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -45,7 +45,6 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": true, "recover_soft_deleted_key_vaults": true, }, }, @@ -79,7 +78,6 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ - "purge_soft_delete_on_destroy": false, "recover_soft_deleted_key_vaults": false, }, }, diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index cfe1ad0bbe97..6e4282b964d2 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -163,9 +163,7 @@ The `features` block supports the following: The `key_vault` block supports the following: -* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. - -~> **Note:** When purge protection is enabled, a vault or an object in the deleted state cannot be purged until the retention period has passed. +* `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Key Vault which has previously been Soft Deleted? Defaults to `true`. --- From 31f5a73f2b0ec74f81dfd850c4eedc8c9b6a8673 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 20 Feb 2020 14:21:12 -0800 Subject: [PATCH 25/33] Start adding purge on destroy back in --- azurerm/internal/provider/features.go | 11 ++++++++++- website/docs/index.html.markdown | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index c0ea99f52950..15d4f72c2276 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -47,7 +47,12 @@ func schemaFeatures() *schema.Schema { Schema: map[string]*schema.Schema{ "recover_soft_deleted_key_vaults": { Type: schema.TypeBool, - Required: true, + Optional: true, + }, + + "purge_soft_delete_on_destroy": { + Type: schema.TypeBool, + Optional: true, }, }, }, @@ -88,6 +93,7 @@ func expandFeatures(input []interface{}) features.UserFeatures { }, KeyVault: features.KeyVaultFeatures{ RecoverSoftDeletedKeyVaults: true, + PurgeSoftDeleteOnDestroy: true, }, } @@ -104,6 +110,9 @@ func expandFeatures(input []interface{}) features.UserFeatures { if v, ok := keyVaultRaw["recover_soft_deleted_key_vaults"]; ok { features.KeyVault.RecoverSoftDeletedKeyVaults = v.(bool) } + if v, ok := keyVaultRaw["purge_soft_delete_on_destroy"]; ok { + features.KeyVault.RecoverSoftDeletedKeyVaults = v.(bool) + } } } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 6e4282b964d2..7e01a4fd22a5 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -163,7 +163,11 @@ The `features` block supports the following: The `key_vault` block supports the following: -* `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Key Vault which has previously been Soft Deleted? Defaults to `true`. +* `recover_soft_deleted_key_vaults` - (Optional) Should the `azurerm_key_vault` resource recover a Key Vault which has previously been Soft Deleted? Defaults to `true`. + +* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. + +~> **Note:** When purge protection is enabled, a vault or an object in the deleted state cannot be purged until the retention period(90 days) has passed. --- From 6a7b3e5875ae1c1d1fbc5097756edf345aa49e07 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline <20408400+WodansSon@users.noreply.github.com> Date: Thu, 20 Feb 2020 18:52:54 -0800 Subject: [PATCH 26/33] Added Purge on Destroy back in --- azurerm/helpers/azure/key_vault.go | 29 +++++++++++++ azurerm/internal/features/user_flags.go | 1 + azurerm/internal/provider/features.go | 8 ++-- azurerm/internal/provider/features_test.go | 14 +++++- .../keyvault/resource_arm_key_vault.go | 43 +++++++++++++++++++ .../tests/resource_arm_key_vault_test.go | 11 +++++ website/docs/index.html.markdown | 2 +- 7 files changed, 101 insertions(+), 7 deletions(-) diff --git a/azurerm/helpers/azure/key_vault.go b/azurerm/helpers/azure/key_vault.go index 7957380bb594..e9459bdec46b 100644 --- a/azurerm/helpers/azure/key_vault.go +++ b/azurerm/helpers/azure/key_vault.go @@ -3,6 +3,7 @@ package azure import ( "context" "fmt" + "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -118,3 +119,31 @@ func KeyVaultExists(ctx context.Context, client *keyvault.VaultsClient, keyVault return true, nil } + +func KeyVaultGetSoftDeletedState(ctx context.Context, client *keyvault.VaultsClient, name string, location string) (deleteDate interface{}, purgeDate interface{}, err error) { + softDel, err := client.GetDeleted(ctx, name, location) + if err != nil { + return nil, nil, fmt.Errorf("unable to get soft delete state information: %+v", err) + } + + // the logic is this way because the GetDeleted call will return an existing key vault + // that is not soft deleted, but the Deleted Vault properties will be nil + if props := softDel.Properties; props != nil { + var delDate interface{} + var purgeDate interface{} + + if dd := props.DeletionDate; dd != nil { + delDate = (*dd).Format(time.RFC3339) + } + if pg := props.ScheduledPurgeDate; pg != nil { + purgeDate = (*pg).Format(time.RFC3339) + } + + if delDate != nil && purgeDate != nil { + return delDate, purgeDate, nil + } + } + + // this means we found an existing key vault that is not soft deleted + return nil, nil, nil +} diff --git a/azurerm/internal/features/user_flags.go b/azurerm/internal/features/user_flags.go index 93407b225c2e..cc874f68dd20 100644 --- a/azurerm/internal/features/user_flags.go +++ b/azurerm/internal/features/user_flags.go @@ -15,5 +15,6 @@ type VirtualMachineScaleSetFeatures struct { } type KeyVaultFeatures struct { + PurgeSoftDeleteOnDestroy bool RecoverSoftDeletedKeyVaults bool } diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 15d4f72c2276..3df1e7014a49 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -92,8 +92,8 @@ func expandFeatures(input []interface{}) features.UserFeatures { RollInstancesWhenRequired: true, }, KeyVault: features.KeyVaultFeatures{ - RecoverSoftDeletedKeyVaults: true, PurgeSoftDeleteOnDestroy: true, + RecoverSoftDeletedKeyVaults: true, }, } @@ -107,10 +107,10 @@ func expandFeatures(input []interface{}) features.UserFeatures { items := raw.([]interface{}) if len(items) > 0 { keyVaultRaw := items[0].(map[string]interface{}) - if v, ok := keyVaultRaw["recover_soft_deleted_key_vaults"]; ok { - features.KeyVault.RecoverSoftDeletedKeyVaults = v.(bool) - } if v, ok := keyVaultRaw["purge_soft_delete_on_destroy"]; ok { + features.KeyVault.PurgeSoftDeleteOnDestroy = v.(bool) + } + if v, ok := keyVaultRaw["recover_soft_deleted_key_vaults"]; ok { features.KeyVault.RecoverSoftDeletedKeyVaults = v.(bool) } } diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index 6cab40a7f8ec..79b54bd58576 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -19,6 +19,7 @@ func TestExpandFeatures(t *testing.T) { Input: []interface{}{}, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, }, VirtualMachine: features.VirtualMachineFeatures{ @@ -45,6 +46,7 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ + "purge_soft_delete_on_destroy": true, "recover_soft_deleted_key_vaults": true, }, }, @@ -52,6 +54,7 @@ func TestExpandFeatures(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, }, VirtualMachine: features.VirtualMachineFeatures{ @@ -78,6 +81,7 @@ func TestExpandFeatures(t *testing.T) { }, "key_vault": []interface{}{ map[string]interface{}{ + "purge_soft_delete_on_destroy": false, "recover_soft_deleted_key_vaults": false, }, }, @@ -85,6 +89,7 @@ func TestExpandFeatures(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: false, RecoverSoftDeletedKeyVaults: false, }, VirtualMachine: features.VirtualMachineFeatures{ @@ -122,16 +127,18 @@ func TestExpandFeaturesKeyVault(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, }, }, }, { - Name: "Recover Soft Deleted Key Vaults Enabled", + Name: "Purge Soft Delete On Destroy and Recover Soft Deleted Key Vaults Enabled", Input: []interface{}{ map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ + "purge_soft_delete_on_destroy": true, "recover_soft_deleted_key_vaults": true, }, }, @@ -139,16 +146,18 @@ func TestExpandFeaturesKeyVault(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, }, }, }, { - Name: "Recover Soft Deleted Key Vaults Disabled", + Name: "Purge Soft Delete On Destroy and Recover Soft Deleted Key Vaults Disabled", Input: []interface{}{ map[string]interface{}{ "key_vault": []interface{}{ map[string]interface{}{ + "purge_soft_delete_on_destroy": false, "recover_soft_deleted_key_vaults": false, }, }, @@ -156,6 +165,7 @@ func TestExpandFeaturesKeyVault(t *testing.T) { }, Expected: features.UserFeatures{ KeyVault: features.KeyVaultFeatures{ + PurgeSoftDeleteOnDestroy: false, RecoverSoftDeletedKeyVaults: false, }, }, diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 2a878c12f6e8..2e38c72edd23 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -570,8 +570,12 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { if err != nil { return err } + resourceGroup := id.ResourceGroup name := id.Path["vaults"] + location := d.Get("location").(string) + purgeProtectionEnabled := false + softDeleteEnabled := false locks.ByName(name, keyVaultResourceName) defer locks.UnlockByName(name, keyVaultResourceName) @@ -589,6 +593,16 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error retrieving Key Vault %q (Resource Group %q): `properties` was nil", name, resourceGroup) } + // Check to see if purge protection is enabled or not... + if read.Properties == nil { + if ppe := read.Properties.EnablePurgeProtection; ppe != nil { + purgeProtectionEnabled = *ppe + } + if sde := read.Properties.EnableSoftDelete; sde != nil { + softDeleteEnabled = *sde + } + } + // ensure we lock on the latest network names, to ensure we handle Azure's networking layer being limited to one change at a time virtualNetworkNames := make([]string, 0) if props := read.Properties; props != nil { @@ -623,6 +637,35 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } } + // Purge the soft deleted key vault permanently if the feature flag is enabled + if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy && softDeleteEnabled { + // Raise an error if the key vault is in the soft delete state and purge protection is enabled + if purgeProtectionEnabled { + delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) + if err == nil { + if delDate != nil && purgeDate != nil { + return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate.(string), purgeDate.(string)) + } + + // I was unable to get the soft deleted and purge date information, error with a generic error as the key valit cannot be purged + return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled", name, resourceGroup) + } + } + + log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) + + future, err := client.PurgeDeleted(ctx, name, location) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for purge of KeyVault %s", name) + err = future.WaitForCompletionRef(ctx, client.Client) + if err != nil { + return fmt.Errorf("Error purging Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + return nil } diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index d357df9493c3..77b6d19b0089 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -1085,6 +1085,17 @@ resource "azurerm_key_vault" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func testAccAzureRMKeyVault_softDeletePurge(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} + func testAccAzureRMKeyVault_purgeProtectionAndSoftDelete(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 7e01a4fd22a5..c8c7a54f6b64 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -167,7 +167,7 @@ The `key_vault` block supports the following: * `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_key_vault` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `true`. -~> **Note:** When purge protection is enabled, a vault or an object in the deleted state cannot be purged until the retention period(90 days) has passed. +~> **Note:** When purge protection is enabled, a key vault or an object in the deleted state cannot be purged until the retention period(90 days) has passed. --- From a349df8d84313db914d37ccf3b5add61d631c78c Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 09:29:07 +0100 Subject: [PATCH 27/33] r/key_vault: fixing up the tests --- .../services/keyvault/tests/resource_arm_key_vault_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index d357df9493c3..83e065aa250e 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -374,7 +374,6 @@ func TestAccAzureRMKeyVault_softDeleteRecoveryDisabled(t *testing.T) { Config: testAccAzureRMKeyVault_softDeleteRecoveryDisabled(data), ExpectError: regexp.MustCompile("An existing soft-deleted Key Vault exists with the Name"), }, - data.ImportStep(), }, }) } @@ -470,7 +469,7 @@ func TestAccAzureRMKeyVault_purgeProtectionAttemptToDisable(t *testing.T) { data.ImportStep(), { Config: testAccAzureRMKeyVault_purgeProtection(data, false), - ExpectError: regexp.MustCompile("TODO"), // TODO: correct error message + ExpectError: regexp.MustCompile("once Purge Protection has been Enabled it's not possible to disable it"), }, }, }) From 3a222df4cce944aafd2e545f97207dd0ffcbffbd Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 14:36:00 +0100 Subject: [PATCH 28/33] r/key_vault: outputting a warning rather than an error during the delete --- azurerm/helpers/azure/key_vault.go | 29 --------- .../keyvault/resource_arm_key_vault.go | 61 +++++++++++++++---- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/azurerm/helpers/azure/key_vault.go b/azurerm/helpers/azure/key_vault.go index e9459bdec46b..7957380bb594 100644 --- a/azurerm/helpers/azure/key_vault.go +++ b/azurerm/helpers/azure/key_vault.go @@ -3,7 +3,6 @@ package azure import ( "context" "fmt" - "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/mgmt/2018-02-14/keyvault" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -119,31 +118,3 @@ func KeyVaultExists(ctx context.Context, client *keyvault.VaultsClient, keyVault return true, nil } - -func KeyVaultGetSoftDeletedState(ctx context.Context, client *keyvault.VaultsClient, name string, location string) (deleteDate interface{}, purgeDate interface{}, err error) { - softDel, err := client.GetDeleted(ctx, name, location) - if err != nil { - return nil, nil, fmt.Errorf("unable to get soft delete state information: %+v", err) - } - - // the logic is this way because the GetDeleted call will return an existing key vault - // that is not soft deleted, but the Deleted Vault properties will be nil - if props := softDel.Properties; props != nil { - var delDate interface{} - var purgeDate interface{} - - if dd := props.DeletionDate; dd != nil { - delDate = (*dd).Format(time.RFC3339) - } - if pg := props.ScheduledPurgeDate; pg != nil { - purgeDate = (*pg).Format(time.RFC3339) - } - - if delDate != nil && purgeDate != nil { - return delDate, purgeDate, nil - } - } - - // this means we found an existing key vault that is not soft deleted - return nil, nil, nil -} diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index 2e38c72edd23..e479667616e1 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -1,6 +1,7 @@ package keyvault import ( + "context" "fmt" "log" "net/http" @@ -639,31 +640,34 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { // Purge the soft deleted key vault permanently if the feature flag is enabled if meta.(*clients.Client).Features.KeyVault.PurgeSoftDeleteOnDestroy && softDeleteEnabled { - // Raise an error if the key vault is in the soft delete state and purge protection is enabled + // KeyVaults with Purge Protection Enabled cannot be deleted unless done by Azure if purgeProtectionEnabled { - delDate, purgeDate, err := azure.KeyVaultGetSoftDeletedState(ctx, client, name, location) - if err == nil { - if delDate != nil && purgeDate != nil { - return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled. The key vault was soft deleted on %s and is scheduled to be purged on %s", name, resourceGroup, delDate.(string), purgeDate.(string)) - } + deletedInfo, err := getSoftDeletedStateForKeyVault(ctx, client, name, location) + if err != nil { + return fmt.Errorf("Error retrieving the Deletion Details for KeyVault %q: %+v", name, err) + } - // I was unable to get the soft deleted and purge date information, error with a generic error as the key valit cannot be purged - return fmt.Errorf("unable to purge Key Vault %q (Resource Group %q) because it has purge protection enabled", name, resourceGroup) + // in the future it'd be nice to raise a warning, but this is the best we can do for now + if deletedInfo != nil { + log.Printf("[DEBUG] The Key Vault %q has Purge Protection Enabled and was deleted on %q. Azure will purge this on %q", name, deletedInfo.deleteDate, deletedInfo.purgeDate) + } else { + log.Printf("[DEBUG] The Key Vault %q has Purge Protection Enabled and will be purged automatically by Azure", name) } + return nil } - log.Printf("[DEBUG] KeyVault %s marked for purge, executing purge", name) - + log.Printf("[DEBUG] KeyVault %q marked for purge - executing purge", name) future, err := client.PurgeDeleted(ctx, name, location) if err != nil { return err } - log.Printf("[DEBUG] Waiting for purge of KeyVault %s", name) + log.Printf("[DEBUG] Waiting for purge of KeyVault %q..", name) err = future.WaitForCompletionRef(ctx, client.Client) if err != nil { return fmt.Errorf("Error purging Key Vault %q (Resource Group %q): %+v", name, resourceGroup, err) } + log.Printf("[DEBUG] Purged KeyVault %q.", name) } return nil @@ -792,3 +796,38 @@ Alternatively you can manually recover this (e.g. using the Azure CLI) and then this into Terraform via "terraform import", or pick a different name/location. `, name, location) } + +type keyVaultDeletionStatus struct { + deleteDate string + purgeDate string +} + +func getSoftDeletedStateForKeyVault(ctx context.Context, client *keyvault.VaultsClient, name string, location string) (*keyVaultDeletionStatus, error) { + softDel, err := client.GetDeleted(ctx, name, location) + if err != nil { + return nil, err + } + + // we found an existing key vault that is not soft deleted + if softDel.Properties == nil { + return nil, nil + } + + // the logic is this way because the GetDeleted call will return an existing key vault + // that is not soft deleted, but the Deleted Vault properties will be nil + props := *softDel.Properties + + result := keyVaultDeletionStatus{} + if props.DeletionDate != nil { + result.deleteDate = (*props.DeletionDate).Format(time.RFC3339) + } + if props.ScheduledPurgeDate != nil { + result.purgeDate = (*props.ScheduledPurgeDate).Format(time.RFC3339) + } + + if result.deleteDate == "" && result.purgeDate == "" { + return nil, nil + } + + return &result, nil +} From e85e4d1df46efbfca083e5364a21289b0319c301 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 14:37:36 +0100 Subject: [PATCH 29/33] r/key_vault: updating the docs --- website/docs/r/key_vault.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/key_vault.html.markdown b/website/docs/r/key_vault.html.markdown index 918591ba6da2..e5ab3ed5c6e0 100644 --- a/website/docs/r/key_vault.html.markdown +++ b/website/docs/r/key_vault.html.markdown @@ -93,7 +93,7 @@ The following arguments are supported: * `purge_protection_enabled` - (Optional) Is Purge Protection enabled for this Key Vault? Defaults to `false`. -!> **Note:** Once Purge Protection has been Enabled it's not possible to Disable it. Support for [disabling purge protection is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075). +!> **Note:** Once Purge Protection has been Enabled it's not possible to Disable it. Support for [disabling purge protection is being tracked in this Azure API issue](https://github.com/Azure/azure-rest-api-specs/issues/8075). Deleting the Key Vault with Purge Protection Enabled will schedule the Key Vault to be deleted (which will happen by Azure in the configured number of days, currently 90 days - which will be configurable in Terraform in the future). * `soft_delete_enabled` - (Optional) Should Soft Delete be enabled for this Key Vault? Defaults to `false`. From 5cc9062615fc39ea7de502d86a5f9c7642cf6bfd Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 14:47:41 +0100 Subject: [PATCH 30/33] r/key_vault: removing a superflurious if statement --- .../services/keyvault/resource_arm_key_vault.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/azurerm/internal/services/keyvault/resource_arm_key_vault.go b/azurerm/internal/services/keyvault/resource_arm_key_vault.go index e479667616e1..bd91a63452ad 100644 --- a/azurerm/internal/services/keyvault/resource_arm_key_vault.go +++ b/azurerm/internal/services/keyvault/resource_arm_key_vault.go @@ -575,8 +575,6 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { resourceGroup := id.ResourceGroup name := id.Path["vaults"] location := d.Get("location").(string) - purgeProtectionEnabled := false - softDeleteEnabled := false locks.ByName(name, keyVaultResourceName) defer locks.UnlockByName(name, keyVaultResourceName) @@ -595,13 +593,13 @@ func resourceArmKeyVaultDelete(d *schema.ResourceData, meta interface{}) error { } // Check to see if purge protection is enabled or not... - if read.Properties == nil { - if ppe := read.Properties.EnablePurgeProtection; ppe != nil { - purgeProtectionEnabled = *ppe - } - if sde := read.Properties.EnableSoftDelete; sde != nil { - softDeleteEnabled = *sde - } + purgeProtectionEnabled := false + if ppe := read.Properties.EnablePurgeProtection; ppe != nil { + purgeProtectionEnabled = *ppe + } + softDeleteEnabled := false + if sde := read.Properties.EnableSoftDelete; sde != nil { + softDeleteEnabled = *sde } // ensure we lock on the latest network names, to ensure we handle Azure's networking layer being limited to one change at a time From dc7b871daf0d257a9bc1298a98d96a6db2831fc8 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 15:39:13 +0100 Subject: [PATCH 31/33] r/keyvault: removing dead code --- .../keyvault/tests/resource_arm_key_vault_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 8bdab40ae03d..83e065aa250e 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -1084,17 +1084,6 @@ resource "azurerm_key_vault" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } -func testAccAzureRMKeyVault_softDeletePurge(data acceptance.TestData) string { - return fmt.Sprintf(` -data "azurerm_client_config" "current" {} - -resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" -} -`, data.RandomInteger, data.Locations.Primary) -} - func testAccAzureRMKeyVault_purgeProtectionAndSoftDelete(data acceptance.TestData) string { return fmt.Sprintf(` data "azurerm_client_config" "current" {} From 415d4fd2d8e0915b99c99508bfd0238d40fe9092 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 16:38:40 +0100 Subject: [PATCH 32/33] r/keyvault: fixing the soft delete disabled test --- .../keyvault/tests/data_source_key_vault_test.go | 3 --- .../keyvault/tests/resource_arm_key_vault_test.go | 10 +++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go index e96326fbc1ad..9ee79eed75f8 100644 --- a/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/data_source_key_vault_test.go @@ -104,9 +104,6 @@ func TestAccDataSourceAzureRMKeyVault_softDelete(t *testing.T) { resource.TestCheckResourceAttr(data.ResourceName, "tags.%", "0"), ), }, - { - Config: testAccAzureRMKeyVault_softDeleteAbsent(data), - }, }, }) } diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index 83e065aa250e..c4472598ea9f 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -357,7 +357,7 @@ func TestAccAzureRMKeyVault_softDeleteRecoveryDisabled(t *testing.T) { Steps: []resource.TestStep{ { // create it regularly - Config: testAccAzureRMKeyVault_softDelete(data, true), + Config: testAccAzureRMKeyVault_softDeleteRecoveryDisabled(data), Check: resource.ComposeTestCheckFunc( testCheckAzureRMKeyVaultExists(data.ResourceName), resource.TestCheckResourceAttr(data.ResourceName, "soft_delete_enabled", "true"), @@ -1047,6 +1047,14 @@ resource "azurerm_key_vault" "test" { func testAccAzureRMKeyVault_softDeleteAbsent(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + recover_soft_deleted_key_vaults = false + } + } +} + data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { From 7bf15efacd60b54762899eb1ceae3a851aafc90b Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 21 Feb 2020 17:00:52 +0100 Subject: [PATCH 33/33] linting --- .../tests/resource_arm_key_vault_test.go | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go index c4472598ea9f..f308e2ae1d5f 100644 --- a/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go +++ b/azurerm/internal/services/keyvault/tests/resource_arm_key_vault_test.go @@ -1035,12 +1035,12 @@ resource "azurerm_resource_group" "test" { } resource "azurerm_key_vault" "test" { - name = "vault%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - tenant_id = data.azurerm_client_config.current.tenant_id - sku_name = "premium" - soft_delete_enabled = %t + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_enabled = %t } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, enabled) } @@ -1049,9 +1049,9 @@ func testAccAzureRMKeyVault_softDeleteAbsent(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features { - key_vault { - recover_soft_deleted_key_vaults = false - } + key_vault { + recover_soft_deleted_key_vaults = false + } } } @@ -1068,9 +1068,9 @@ func testAccAzureRMKeyVault_softDeleteRecoveryDisabled(data acceptance.TestData) return fmt.Sprintf(` provider "azurerm" { features { - key_vault { - recover_soft_deleted_key_vaults = false - } + key_vault { + recover_soft_deleted_key_vaults = false + } } } @@ -1082,12 +1082,12 @@ resource "azurerm_resource_group" "test" { } resource "azurerm_key_vault" "test" { - name = "vault%d" - location = azurerm_resource_group.test.location - resource_group_name = azurerm_resource_group.test.name - tenant_id = data.azurerm_client_config.current.tenant_id - sku_name = "premium" - soft_delete_enabled = true + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_enabled = true } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) }