From 32a2beae101ff006be2a378cdfa0f2b141382c58 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 15 Apr 2024 14:38:39 -0700 Subject: [PATCH 1/3] machine_learning - Add new machine_learning block that supports purge_protected_items_from_vault_on_destroy --- internal/features/defaults.go | 3 + internal/features/user_flags.go | 5 + internal/provider/features.go | 24 ++ internal/provider/features_test.go | 84 +++++++ .../machine_learning_workspace_resource.go | 9 +- ...achine_learning_workspace_resource_test.go | 207 +++++++++++++++++- .../docs/guides/features-block.html.markdown | 12 + 7 files changed, 331 insertions(+), 13 deletions(-) diff --git a/internal/features/defaults.go b/internal/features/defaults.go index 49c64d56659e..34b35da890c4 100644 --- a/internal/features/defaults.go +++ b/internal/features/defaults.go @@ -60,5 +60,8 @@ func Default() UserFeatures { PostgresqlFlexibleServer: PostgresqlFlexibleServerFeatures{ RestartServerOnConfigurationValueChange: true, }, + MachineLearning: MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: false, + }, } } diff --git a/internal/features/user_flags.go b/internal/features/user_flags.go index 00d0199cd775..0b204862196f 100644 --- a/internal/features/user_flags.go +++ b/internal/features/user_flags.go @@ -17,6 +17,7 @@ type UserFeatures struct { ManagedDisk ManagedDiskFeatures Subscription SubscriptionFeatures PostgresqlFlexibleServer PostgresqlFlexibleServerFeatures + MachineLearning MachineLearningFeatures } type CognitiveAccountFeatures struct { @@ -85,3 +86,7 @@ type SubscriptionFeatures struct { type PostgresqlFlexibleServerFeatures struct { RestartServerOnConfigurationValueChange bool } + +type MachineLearningFeatures struct { + PurgeSoftDeletedWorkspaceOnDestroy bool +} diff --git a/internal/provider/features.go b/internal/provider/features.go index e16c7d6c1ad9..49ee85fb0c25 100644 --- a/internal/provider/features.go +++ b/internal/provider/features.go @@ -304,6 +304,20 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema { }, }, }, + "machine_learning": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "purge_soft_deleted_workspace_on_destroy": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, } // this is a temporary hack to enable us to gradually add provider blocks to test configurations @@ -514,5 +528,15 @@ func expandFeatures(input []interface{}) features.UserFeatures { } } + if raw, ok := val["machine_learning"]; ok { + items := raw.([]interface{}) + if len(items) > 0 { + subscriptionRaw := items[0].(map[string]interface{}) + if v, ok := subscriptionRaw["purge_soft_deleted_workspace_on_destroy"]; ok { + featuresMap.MachineLearning.PurgeSoftDeletedWorkspaceOnDestroy = v.(bool) + } + } + } + return featuresMap } diff --git a/internal/provider/features_test.go b/internal/provider/features_test.go index 544f514477a3..032b05b01791 100644 --- a/internal/provider/features_test.go +++ b/internal/provider/features_test.go @@ -75,6 +75,9 @@ func TestExpandFeatures(t *testing.T) { PostgresqlFlexibleServer: features.PostgresqlFlexibleServerFeatures{ RestartServerOnConfigurationValueChange: true, }, + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: false, + }, }, }, { @@ -161,6 +164,11 @@ func TestExpandFeatures(t *testing.T) { "scale_to_zero_before_deletion": true, }, }, + "machine_learning": []interface{}{ + map[string]interface{}{ + "purge_soft_deleted_workspace_on_destroy": true, + }, + }, }, }, Expected: features.UserFeatures{ @@ -218,6 +226,9 @@ func TestExpandFeatures(t *testing.T) { PostgresqlFlexibleServer: features.PostgresqlFlexibleServerFeatures{ RestartServerOnConfigurationValueChange: true, }, + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: true, + }, }, }, { @@ -304,6 +315,11 @@ func TestExpandFeatures(t *testing.T) { "scale_to_zero_before_deletion": false, }, }, + "machine_learning": []interface{}{ + map[string]interface{}{ + "purge_soft_deleted_workspace_on_destroy": false, + }, + }, }, }, Expected: features.UserFeatures{ @@ -361,6 +377,9 @@ func TestExpandFeatures(t *testing.T) { PostgresqlFlexibleServer: features.PostgresqlFlexibleServerFeatures{ RestartServerOnConfigurationValueChange: false, }, + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: false, + }, }, }, } @@ -1358,3 +1377,68 @@ func TestExpandFeaturesPosgresqlFlexibleServer(t *testing.T) { } } } + +func TestExpandFeaturesMachineLearning(t *testing.T) { + testData := []struct { + Name string + Input []interface{} + EnvVars map[string]interface{} + Expected features.UserFeatures + }{ + { + Name: "Empty Block", + Input: []interface{}{ + map[string]interface{}{ + "machine_learning": []interface{}{}, + }, + }, + Expected: features.UserFeatures{ + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: false, + }, + }, + }, + { + Name: "MachineLearning Workspace purge soft delete on destroy", + Input: []interface{}{ + map[string]interface{}{ + "machine_learning": []interface{}{ + map[string]interface{}{ + "purge_soft_deleted_workspace_on_destroy": true, + }, + }, + }, + }, + Expected: features.UserFeatures{ + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: true, + }, + }, + }, + { + Name: "MachineLearning Workspace does not purge soft delete on destroy", + Input: []interface{}{ + map[string]interface{}{ + "machine_learning": []interface{}{ + map[string]interface{}{ + "purge_soft_deleted_workspace_on_destroy": false, + }, + }, + }, + }, + Expected: features.UserFeatures{ + MachineLearning: features.MachineLearningFeatures{ + PurgeSoftDeletedWorkspaceOnDestroy: false, + }, + }, + }, + } + + for _, testCase := range testData { + t.Logf("[DEBUG] Test Case: %q", testCase.Name) + result := expandFeatures(testCase.Input) + if !reflect.DeepEqual(result.Subscription, testCase.Expected.Subscription) { + t.Fatalf("Expected %+v but got %+v", result.Subscription, testCase.Expected.Subscription) + } + } +} diff --git a/internal/services/machinelearning/machine_learning_workspace_resource.go b/internal/services/machinelearning/machine_learning_workspace_resource.go index 9be42b7046b5..37abec0f114a 100644 --- a/internal/services/machinelearning/machine_learning_workspace_resource.go +++ b/internal/services/machinelearning/machine_learning_workspace_resource.go @@ -461,7 +461,14 @@ func resourceMachineLearningWorkspaceDelete(d *pluginsdk.ResourceData, meta inte return fmt.Errorf("parsing Machine Learning Workspace ID `%q`: %+v", d.Id(), err) } - future, err := client.Delete(ctx, *id, workspaces.DefaultDeleteOperationOptions()) + options := workspaces.DefaultDeleteOperationOptions() + if meta.(*clients.Client).Features.MachineLearning.PurgeSoftDeletedWorkspaceOnDestroy { + options = workspaces.DeleteOperationOptions{ + ForceToPurge: pointer.To(true), + } + } + + future, err := client.Delete(ctx, *id, options) if err != nil { return fmt.Errorf("deleting Machine Learning Workspace %q (Resource Group %q): %+v", id.WorkspaceName, id.ResourceGroupName, err) } diff --git a/internal/services/machinelearning/machine_learning_workspace_resource_test.go b/internal/services/machinelearning/machine_learning_workspace_resource_test.go index 32f7f9b04246..0362af996d76 100644 --- a/internal/services/machinelearning/machine_learning_workspace_resource_test.go +++ b/internal/services/machinelearning/machine_learning_workspace_resource_test.go @@ -334,6 +334,25 @@ func TestAccMachineLearningWorkspace_kindUpdate(t *testing.T) { }) } +func TestAccMachineLearningWorkspace_purgeSoftDelete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_workspace", "test") + r := WorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.purgeSoftDelete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("identity.#").HasValue("1"), + check.That(data.ResourceName).Key("identity.0.type").HasValue("SystemAssigned"), + check.That(data.ResourceName).Key("identity.0.principal_id").Exists(), + check.That(data.ResourceName).Key("identity.0.tenant_id").Exists(), + ), + }, + data.ImportStep(), + }) +} + func (r WorkspaceResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { workspacesClient := client.MachineLearning.Workspaces id, err := workspaces.ParseWorkspaceID(state.ID) @@ -355,6 +374,18 @@ func (r WorkspaceResource) Exists(ctx context.Context, client *clients.Client, s func (r WorkspaceResource) basic(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %s resource "azurerm_machine_learning_workspace" "test" { @@ -375,6 +406,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) basicUpdated(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %s resource "azurerm_machine_learning_workspace" "test" { @@ -401,6 +444,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) complete(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_container_registry" "test" { @@ -468,6 +523,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) completeUpdated(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_container_registry" "test" { @@ -550,18 +617,6 @@ resource "azurerm_machine_learning_workspace" "import" { func (r WorkspaceResource) template(data acceptance.TestData) string { return fmt.Sprintf(` -provider "azurerm" { - features { - key_vault { - purge_soft_delete_on_destroy = false - purge_soft_deleted_keys_on_destroy = false - } - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} - data "azurerm_client_config" "current" {} resource "azurerm_resource_group" "test" { @@ -613,6 +668,18 @@ resource "azurerm_storage_account" "test" { func (r WorkspaceResource) userAssignedIdentity(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_user_assigned_identity" "test" { @@ -649,6 +716,18 @@ resource "azurerm_machine_learning_workspace" "test" { } func (r WorkspaceResource) userAssignedIdentityUpdate(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_user_assigned_identity" "test" { @@ -699,6 +778,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) systemAssignedUserAssignedIdentity(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_user_assigned_identity" "test" { @@ -727,6 +818,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) systemAssignedIdentity(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_user_assigned_identity" "test" { @@ -752,6 +855,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) systemAssignedAndCustomManagedKey(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s resource "azurerm_key_vault_key" "test" { @@ -808,6 +923,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) userAssignedAndCustomManagedKey(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %[1]s data "azuread_service_principal" "test" { @@ -931,6 +1058,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) featureStore(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %s resource "azurerm_machine_learning_workspace" "test" { @@ -959,6 +1098,18 @@ resource "azurerm_machine_learning_workspace" "test" { func (r WorkspaceResource) featureStoreUpdate(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + %s resource "azurerm_machine_learning_workspace" "test" { @@ -983,3 +1134,35 @@ resource "azurerm_machine_learning_workspace" "test" { } `, template, data.RandomInteger) } + +func (r WorkspaceResource) purgeSoftDelete(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + machine_learning { + purge_soft_deleted_workspace_on_destroy = true + } + } +} + +%s + +resource "azurerm_machine_learning_workspace" "test" { + name = "acctest-MLW-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + key_vault_id = azurerm_key_vault.test.id + storage_account_id = azurerm_storage_account.test.id + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} diff --git a/website/docs/guides/features-block.html.markdown b/website/docs/guides/features-block.html.markdown index 831f2e6692b8..99c755dc1847 100644 --- a/website/docs/guides/features-block.html.markdown +++ b/website/docs/guides/features-block.html.markdown @@ -53,6 +53,10 @@ provider "azurerm" { log_analytics_workspace { permanently_delete_on_destroy = true } + + machine_learning { + purge_soft_deleted_workspace_on_destroy = true + } managed_disk { expand_without_downtime = true @@ -105,6 +109,8 @@ The `features` block supports the following: * `log_analytics_workspace` - (Optional) A `log_analytics_workspace` block as defined below. +* `machine_learning` - (Optional) A `machine_learning` block as defined below. + * `managed_disk` - (Optional) A `managed_disk` block as defined below. * `resource_group` - (Optional) A `resource_group` block as defined below. @@ -179,6 +185,12 @@ The `log_analytics_workspace` block supports the following: --- +The `machine_learning` block supports the following: + +* `purge_soft_deleted_workspace_on_destroy` - (Optional) Should the `azurerm_machine_learning_workspace` resource be permanently deleted (e.g. purged) when destoryed? Defaults to `false`. + +--- + The `managed_disk` block supports the following: * `expand_without_downtime` - (Optional) Specifies whether Managed Disks which can be Expanded without Downtime (on either [a Linux VM](https://learn.microsoft.com/azure/virtual-machines/linux/expand-disks?tabs=azure-cli%2Cubuntu#expand-without-downtime) [or a Windows VM](https://learn.microsoft.com/azure/virtual-machines/windows/expand-os-disk#expand-without-downtime)) should be expanded without restarting the associated Virtual Machine. Defaults to `true`. From add2c348baff6e60fb5470b3a99de693ee3e0fe5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 15 Apr 2024 15:03:54 -0700 Subject: [PATCH 2/3] terrafmt --- website/docs/guides/features-block.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/features-block.html.markdown b/website/docs/guides/features-block.html.markdown index d48f11e52ea6..4d23b279acc2 100644 --- a/website/docs/guides/features-block.html.markdown +++ b/website/docs/guides/features-block.html.markdown @@ -53,7 +53,7 @@ provider "azurerm" { log_analytics_workspace { permanently_delete_on_destroy = true } - + machine_learning { purge_soft_deleted_workspace_on_destroy = true } From ebe67f1faadb57d568986da768a85f3e7757b858 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 15 Apr 2024 16:42:14 -0700 Subject: [PATCH 3/3] terrafmt --- website/docs/guides/features-block.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/guides/features-block.html.markdown b/website/docs/guides/features-block.html.markdown index 4d23b279acc2..2e2ddae26ad9 100644 --- a/website/docs/guides/features-block.html.markdown +++ b/website/docs/guides/features-block.html.markdown @@ -194,7 +194,7 @@ The `log_analytics_workspace` block supports the following: The `machine_learning` block supports the following: -* `purge_soft_deleted_workspace_on_destroy` - (Optional) Should the `azurerm_machine_learning_workspace` resource be permanently deleted (e.g. purged) when destoryed? Defaults to `false`. +* `purge_soft_deleted_workspace_on_destroy` - (Optional) Should the `azurerm_machine_learning_workspace` resource be permanently deleted (e.g. purged) when destroyed? Defaults to `false`. ---