From 2cdcd9cdcb567c35a66deb32f53d31d89666927f Mon Sep 17 00:00:00 2001 From: yupwei68 Date: Wed, 26 May 2021 14:23:55 +0800 Subject: [PATCH 1/6] update --- .../storage/storage_account_resource.go | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/azurerm/internal/services/storage/storage_account_resource.go b/azurerm/internal/services/storage/storage_account_resource.go index 8a41cab84efe..2882b9192109 100644 --- a/azurerm/internal/services/storage/storage_account_resource.go +++ b/azurerm/internal/services/storage/storage_account_resource.go @@ -934,6 +934,12 @@ func resourceStorageAccountCreate(d *pluginsdk.ResourceData, meta interface{}) e blobProperties := expandBlobProperties(val.([]interface{})) + if v := d.Get("blob_properties.0.last_access_time_enabled").(bool); v { + blobProperties.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ + Enable: utils.Bool(v), + } + } + if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, *blobProperties); err != nil { return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err) } @@ -1268,6 +1274,16 @@ func resourceStorageAccountUpdate(d *pluginsdk.ResourceData, meta interface{}) e blobClient := meta.(*clients.Client).Storage.BlobServicesClient blobProperties := expandBlobProperties(d.Get("blob_properties").([]interface{})) + if d.HasChange("blob_properties.0.last_access_time_enabled") { + lastAccessTimeTracking := false + if v := d.Get("blob_properties.0.last_access_time_enabled").(bool); v { + lastAccessTimeTracking = true + } + blobProperties.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ + Enable: utils.Bool(lastAccessTimeTracking), + } + } + if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, *blobProperties); err != nil { return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err) } @@ -1806,10 +1822,6 @@ func expandBlobProperties(input []interface{}) *storage.BlobServiceProperties { ChangeFeed: &storage.ChangeFeed{ Enabled: utils.Bool(false), }, - LastAccessTimeTrackingPolicy: &storage.LastAccessTimeTrackingPolicy{ - Enable: utils.Bool(false), - }, - DeleteRetentionPolicy: &storage.DeleteRetentionPolicy{ Enabled: utils.Bool(false), }, @@ -1839,9 +1851,6 @@ func expandBlobProperties(input []interface{}) *storage.BlobServiceProperties { props.DefaultServiceVersion = utils.String(version) } - props.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ - Enable: utils.Bool(v["last_access_time_enabled"].(bool)), - } return &props } From 420097aea475404628519522fb8de050c7189211 Mon Sep 17 00:00:00 2001 From: yupwei68 Date: Tue, 1 Jun 2021 14:42:33 +0800 Subject: [PATCH 2/6] revert --- .../storage/storage_account_resource.go | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/azurerm/internal/services/storage/storage_account_resource.go b/azurerm/internal/services/storage/storage_account_resource.go index d2ef87926891..ba3c153654f6 100644 --- a/azurerm/internal/services/storage/storage_account_resource.go +++ b/azurerm/internal/services/storage/storage_account_resource.go @@ -933,12 +933,6 @@ func resourceStorageAccountCreate(d *pluginsdk.ResourceData, meta interface{}) e blobProperties := expandBlobProperties(val.([]interface{})) - if v := d.Get("blob_properties.0.last_access_time_enabled").(bool); v { - blobProperties.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ - Enable: utils.Bool(v), - } - } - if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, *blobProperties); err != nil { return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err) } @@ -1273,16 +1267,6 @@ func resourceStorageAccountUpdate(d *pluginsdk.ResourceData, meta interface{}) e blobClient := meta.(*clients.Client).Storage.BlobServicesClient blobProperties := expandBlobProperties(d.Get("blob_properties").([]interface{})) - if d.HasChange("blob_properties.0.last_access_time_enabled") { - lastAccessTimeTracking := false - if v := d.Get("blob_properties.0.last_access_time_enabled").(bool); v { - lastAccessTimeTracking = true - } - blobProperties.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ - Enable: utils.Bool(lastAccessTimeTracking), - } - } - if _, err = blobClient.SetServiceProperties(ctx, resourceGroupName, storageAccountName, *blobProperties); err != nil { return fmt.Errorf("Error updating Azure Storage Account `blob_properties` %q: %+v", storageAccountName, err) } @@ -1821,6 +1805,10 @@ func expandBlobProperties(input []interface{}) *storage.BlobServiceProperties { ChangeFeed: &storage.ChangeFeed{ Enabled: utils.Bool(false), }, + LastAccessTimeTrackingPolicy: &storage.LastAccessTimeTrackingPolicy{ + Enable: utils.Bool(false), + }, + DeleteRetentionPolicy: &storage.DeleteRetentionPolicy{ Enabled: utils.Bool(false), }, @@ -1850,6 +1838,9 @@ func expandBlobProperties(input []interface{}) *storage.BlobServiceProperties { props.DefaultServiceVersion = utils.String(version) } + props.LastAccessTimeTrackingPolicy = &storage.LastAccessTimeTrackingPolicy{ + Enable: utils.Bool(v["last_access_time_enabled"].(bool)), + } return &props } From 4f8981c813158b1406691c6c9d161131b232e3fe Mon Sep 17 00:00:00 2001 From: yupwei68 Date: Wed, 9 Jun 2021 15:32:05 +0800 Subject: [PATCH 3/6] update --- azurerm/internal/features/user_flags.go | 5 ++ azurerm/internal/provider/features.go | 24 ++++++ azurerm/internal/provider/features_test.go | 84 +++++++++++++++++++ .../apimanagement/api_management_resource.go | 22 +++++ .../api_management_resource_test.go | 61 ++++++++++++++ .../services/apimanagement/client/client.go | 5 ++ 6 files changed, 201 insertions(+) diff --git a/azurerm/internal/features/user_flags.go b/azurerm/internal/features/user_flags.go index d66c53e60891..2bee63e74d88 100644 --- a/azurerm/internal/features/user_flags.go +++ b/azurerm/internal/features/user_flags.go @@ -1,6 +1,7 @@ package features type UserFeatures struct { + ApiManagement ApiManagementFeatures VirtualMachine VirtualMachineFeatures VirtualMachineScaleSet VirtualMachineScaleSetFeatures KeyVault KeyVaultFeatures @@ -36,3 +37,7 @@ type TemplateDeploymentFeatures struct { type LogAnalyticsWorkspaceFeatures struct { PermanentlyDeleteOnDestroy bool } + +type ApiManagementFeatures struct { + PurgeSoftDeleteOnDestroy bool +} diff --git a/azurerm/internal/provider/features.go b/azurerm/internal/provider/features.go index 688e834177fc..de615d8b508b 100644 --- a/azurerm/internal/provider/features.go +++ b/azurerm/internal/provider/features.go @@ -10,6 +10,20 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.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]*pluginsdk.Schema{ + "api_management": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "purge_soft_delete_on_destroy": { + Type: pluginsdk.TypeBool, + Optional: true, + }, + }, + }, + }, + // lintignore:XS003 "key_vault": { Type: pluginsdk.TypeList, @@ -145,6 +159,16 @@ func expandFeatures(input []interface{}) features.UserFeatures { val := input[0].(map[string]interface{}) + if raw, ok := val["api_management"]; ok { + items := raw.([]interface{}) + if len(items) > 0 && items[0] != nil { + apimRaw := items[0].(map[string]interface{}) + if v, ok := apimRaw["purge_soft_delete_on_destroy"]; ok { + features.ApiManagement.PurgeSoftDeleteOnDestroy = v.(bool) + } + } + } + if raw, ok := val["key_vault"]; ok { items := raw.([]interface{}) if len(items) > 0 && items[0] != nil { diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index f429f83ad19e..6cfcb0158a37 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -18,6 +18,9 @@ func TestExpandFeatures(t *testing.T) { Name: "Empty Block", Input: []interface{}{}, Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, KeyVault: features.KeyVaultFeatures{ PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, @@ -46,6 +49,11 @@ func TestExpandFeatures(t *testing.T) { Name: "Complete Enabled", Input: []interface{}{ map[string]interface{}{ + "api_management": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": true, + }, + }, "key_vault": []interface{}{ map[string]interface{}{ "purge_soft_delete_on_destroy": true, @@ -83,6 +91,9 @@ func TestExpandFeatures(t *testing.T) { }, }, Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, KeyVault: features.KeyVaultFeatures{ PurgeSoftDeleteOnDestroy: true, RecoverSoftDeletedKeyVaults: true, @@ -111,6 +122,11 @@ func TestExpandFeatures(t *testing.T) { Name: "Complete Disabled", Input: []interface{}{ map[string]interface{}{ + "api_management": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": false, + }, + }, "key_vault": []interface{}{ map[string]interface{}{ "purge_soft_delete_on_destroy": false, @@ -148,6 +164,9 @@ func TestExpandFeatures(t *testing.T) { }, }, Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, KeyVault: features.KeyVaultFeatures{ PurgeSoftDeleteOnDestroy: false, RecoverSoftDeletedKeyVaults: false, @@ -183,6 +202,71 @@ func TestExpandFeatures(t *testing.T) { } } +func TestExpandFeaturesApiManagement(t *testing.T) { + testData := []struct { + Name string + Input []interface{} + EnvVars map[string]interface{} + Expected features.UserFeatures + }{ + { + Name: "Empty Block", + Input: []interface{}{ + map[string]interface{}{ + "api_management": []interface{}{}, + }, + }, + Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, + }, + }, + { + Name: "Purge Soft Delete On Destroy Api Management Enabled", + Input: []interface{}{ + map[string]interface{}{ + "api_management": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": true, + }, + }, + }, + }, + Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: true, + }, + }, + }, + { + Name: "Purge Soft Delete On Destroy Api Management Disabled", + Input: []interface{}{ + map[string]interface{}{ + "api_management": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": false, + }, + }, + }, + }, + Expected: features.UserFeatures{ + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, + }, + }, + } + + for _, testCase := range testData { + t.Logf("[DEBUG] Test Case: %q", testCase.Name) + result := expandFeatures(testCase.Input) + if !reflect.DeepEqual(result.ApiManagement, testCase.Expected.ApiManagement) { + t.Fatalf("Expected %+v but got %+v", result.ApiManagement, testCase.Expected.ApiManagement) + } + } +} + func TestExpandFeaturesKeyVault(t *testing.T) { testData := []struct { Name string diff --git a/azurerm/internal/services/apimanagement/api_management_resource.go b/azurerm/internal/services/apimanagement/api_management_resource.go index ebabd0de34e9..e4b0cf24c61d 100644 --- a/azurerm/internal/services/apimanagement/api_management_resource.go +++ b/azurerm/internal/services/apimanagement/api_management_resource.go @@ -907,6 +907,28 @@ func resourceApiManagementServiceDelete(d *pluginsdk.ResourceData, meta interfac } } + // Purge the soft deleted Api Management permanently if the feature flag is enabled + if meta.(*clients.Client).Features.ApiManagement.PurgeSoftDeleteOnDestroy { + log.Printf("[DEBUG] Api Management %q marked for purge - executing purge", id) + deletedServicesClient := meta.(*clients.Client).ApiManagement.DeletedServicesClient + _, err := deletedServicesClient.GetByName(ctx, id.ServiceName, azure.NormalizeLocation(d.Get("location").(string))) + if err != nil { + return err + } + future, err := deletedServicesClient.Purge(ctx, id.ServiceName, azure.NormalizeLocation(d.Get("location").(string))) + if err != nil { + return err + } + + log.Printf("[DEBUG] Waiting for purge of Api Management %q..", id) + err = future.WaitForCompletionRef(ctx, deletedServicesClient.Client) + if err != nil { + return fmt.Errorf("purging %s: %+v", *id, err) + } + log.Printf("[DEBUG] Purged Api Management %q.", id) + return nil + } + return nil } diff --git a/azurerm/internal/services/apimanagement/api_management_resource_test.go b/azurerm/internal/services/apimanagement/api_management_resource_test.go index 7c48a9bbf789..384492a857b0 100644 --- a/azurerm/internal/services/apimanagement/api_management_resource_test.go +++ b/azurerm/internal/services/apimanagement/api_management_resource_test.go @@ -292,6 +292,35 @@ func TestAccApiManagement_consumption(t *testing.T) { }) } +func TestAccApiManagement_purgeSoftDelete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management", "test") + r := ApiManagementResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.consumptionPurgeSoftDeleteRecovery(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.consumptionPurgeSoftDelete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).DoesNotExistInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.consumptionPurgeSoftDeleteRecovery(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (ApiManagementResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.ApiManagementID(state.ID) if err != nil { @@ -1421,6 +1450,38 @@ resource "azurerm_api_management" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (r ApiManagementResource) consumptionPurgeSoftDeleteRecovery(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_api_management" "test" { + name = "acctestAM-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + publisher_name = "pub1" + publisher_email = "pub1@email.com" + sku_name = "Consumption_0" +} +`, r.consumptionPurgeSoftDelete(data), data.RandomInteger) +} + +func (ApiManagementResource) consumptionPurgeSoftDelete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + api_management { + purge_soft_delete_on_destroy = true + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} + func (ApiManagementResource) tenantAccess(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/azurerm/internal/services/apimanagement/client/client.go b/azurerm/internal/services/apimanagement/client/client.go index 77419235bb95..9a001e9374a5 100644 --- a/azurerm/internal/services/apimanagement/client/client.go +++ b/azurerm/internal/services/apimanagement/client/client.go @@ -17,6 +17,7 @@ type Client struct { BackendClient *apimanagement.BackendClient CertificatesClient *apimanagement.CertificateClient DiagnosticClient *apimanagement.DiagnosticClient + DeletedServicesClient *apimanagement.DeletedServicesClient EmailTemplateClient *apimanagement.EmailTemplateClient GroupClient *apimanagement.GroupClient GroupUsersClient *apimanagement.GroupUserClient @@ -71,6 +72,9 @@ func NewClient(o *common.ClientOptions) *Client { diagnosticClient := apimanagement.NewDiagnosticClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&diagnosticClient.Client, o.ResourceManagerAuthorizer) + deletedServicesClient := apimanagement.NewDeletedServicesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&deletedServicesClient.Client, o.ResourceManagerAuthorizer) + emailTemplateClient := apimanagement.NewEmailTemplateClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&emailTemplateClient.Client, o.ResourceManagerAuthorizer) @@ -137,6 +141,7 @@ func NewClient(o *common.ClientOptions) *Client { BackendClient: &backendClient, CertificatesClient: &certificatesClient, DiagnosticClient: &diagnosticClient, + DeletedServicesClient: &deletedServicesClient, EmailTemplateClient: &emailTemplateClient, GroupClient: &groupClient, GroupUsersClient: &groupUsersClient, From 60c19c75d1f1df7523a99b60313ac8b0ecdcca86 Mon Sep 17 00:00:00 2001 From: yupwei68 Date: Thu, 22 Jul 2021 11:36:44 +0800 Subject: [PATCH 4/6] update --- .../apimanagement/api_management_resource_test.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/services/apimanagement/api_management_resource_test.go b/azurerm/internal/services/apimanagement/api_management_resource_test.go index 384492a857b0..fa1251a892d9 100644 --- a/azurerm/internal/services/apimanagement/api_management_resource_test.go +++ b/azurerm/internal/services/apimanagement/api_management_resource_test.go @@ -306,11 +306,7 @@ func TestAccApiManagement_purgeSoftDelete(t *testing.T) { data.ImportStep(), { Config: r.consumptionPurgeSoftDelete(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).DoesNotExistInAzure(r), - ), }, - data.ImportStep(), { Config: r.consumptionPurgeSoftDeleteRecovery(data), Check: acceptance.ComposeTestCheckFunc( @@ -1452,7 +1448,14 @@ resource "azurerm_api_management" "test" { func (r ApiManagementResource) consumptionPurgeSoftDeleteRecovery(data acceptance.TestData) string { return fmt.Sprintf(` -%s +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} resource "azurerm_api_management" "test" { name = "acctestAM-%d" @@ -1462,7 +1465,7 @@ resource "azurerm_api_management" "test" { publisher_email = "pub1@email.com" sku_name = "Consumption_0" } -`, r.consumptionPurgeSoftDelete(data), data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } func (ApiManagementResource) consumptionPurgeSoftDelete(data acceptance.TestData) string { From 12bdb0d433a5f591f9ab8bf1627ed0d8305f0111 Mon Sep 17 00:00:00 2001 From: yupwei68 Date: Thu, 5 Aug 2021 10:35:52 +0800 Subject: [PATCH 5/6] update --- azurerm/internal/provider/features_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/provider/features_test.go b/azurerm/internal/provider/features_test.go index 5881680ffd4c..78d6ff9f4b50 100644 --- a/azurerm/internal/provider/features_test.go +++ b/azurerm/internal/provider/features_test.go @@ -52,8 +52,10 @@ func TestExpandFeatures(t *testing.T) { Name: "Complete Enabled", Input: []interface{}{ map[string]interface{}{ - ApiManagement: features.ApiManagementFeatures{ - PurgeSoftDeleteOnDestroy: false, + "api_management": []interface{}{ + map[string]interface{}{ + "purge_soft_delete_on_destroy": true, + }, }, "cognitive_account": []interface{}{ map[string]interface{}{ @@ -178,10 +180,8 @@ func TestExpandFeatures(t *testing.T) { }, }, Expected: features.UserFeatures{ - "api_management": []interface{}{ - map[string]interface{}{ - "purge_soft_delete_on_destroy": false, - }, + ApiManagement: features.ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, }, CognitiveAccount: features.CognitiveAccountFeatures{ PurgeSoftDeleteOnDestroy: false, From 22b4e302f8ccc5d11cd7efdb7cba195d61268a01 Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 15 Sep 2021 14:13:36 +0800 Subject: [PATCH 6/6] complement the document and add default value --- internal/features/defaults.go | 3 +++ website/docs/index.html.markdown | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/internal/features/defaults.go b/internal/features/defaults.go index 05bd9d208262..5f187e3b256b 100644 --- a/internal/features/defaults.go +++ b/internal/features/defaults.go @@ -3,6 +3,9 @@ package features func Default() UserFeatures { return UserFeatures{ // NOTE: ensure all nested objects are fully populated + ApiManagement: ApiManagementFeatures{ + PurgeSoftDeleteOnDestroy: false, + }, CognitiveAccount: CognitiveAccountFeatures{ PurgeSoftDeleteOnDestroy: true, }, diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index f567878896a3..280add554eae 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -169,6 +169,8 @@ It's possible to configure the behaviour of certain resources using the `feature The `features` block supports the following: +* `api_management` - (Optional) An `api_management` block as defined below. + * `cognitive_account` - (Optional) A `cognitive_account` block as defined below. * `key_vault` - (Optional) A `key_vault` block as defined below. @@ -185,6 +187,12 @@ The `features` block supports the following: --- +The `api_management` block supports the following: + +* `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_api_management` resources be permanently deleted (e.g. purged) when destroyed? Defaults to `false`. + +--- + The `cognitive_account` block supports the following: * `purge_soft_delete_on_destroy` - (Optional) Should the `azurerm_cognitive_account` resources be permanently deleted (e.g. purged) when destroyed? Defaults to `true`.