Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_backup_protected_vm - protection can now be suspended during destroy #27950

Merged
merged 10 commits into from
Jan 21, 2025
5 changes: 3 additions & 2 deletions internal/features/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ func Default() UserFeatures {
PurgeSoftDeletedWorkspaceOnDestroy: false,
},
RecoveryService: RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
VMBackupStopProtectionAndRetainDataOnDestroy: false,
VMBackupSuspendProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
},
NetApp: NetAppFeatures{
DeleteBackupsOnBackupVaultDestroy: false,
Expand Down
5 changes: 3 additions & 2 deletions internal/features/user_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ type MachineLearningFeatures struct {
}

type RecoveryServiceFeatures struct {
VMBackupStopProtectionAndRetainDataOnDestroy bool
PurgeProtectedItemsFromVaultOnDestroy bool
VMBackupStopProtectionAndRetainDataOnDestroy bool
VMBackupSuspendProtectionAndRetainDataOnDestroy bool
PurgeProtectedItemsFromVaultOnDestroy bool
}

type NetAppFeatures struct {
Expand Down
18 changes: 15 additions & 3 deletions internal/provider/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,16 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema {
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"vm_backup_stop_protection_and_retain_data_on_destroy": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
ExactlyOneOf: []string{"features.0.recovery_service.0.vm_backup_stop_protection_and_retain_data_on_destroy", "features.0.recovery_service.0.vm_backup_suspend_protection_and_retain_data_on_destroy"},
},
"vm_backup_suspend_protection_and_retain_data_on_destroy": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
ExactlyOneOf: []string{"features.0.recovery_service.0.vm_backup_stop_protection_and_retain_data_on_destroy", "features.0.recovery_service.0.vm_backup_suspend_protection_and_retain_data_on_destroy"},
},
"purge_protected_items_from_vault_on_destroy": {
Type: pluginsdk.TypeBool,
Expand Down Expand Up @@ -417,6 +424,8 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema {
return &pluginsdk.Schema{
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
MinItems: 1,
Elem: &pluginsdk.Resource{
Schema: featuresMap,
},
Expand Down Expand Up @@ -664,6 +673,9 @@ func expandFeatures(input []interface{}) features.UserFeatures {
if v, ok := recoveryServicesRaw["vm_backup_stop_protection_and_retain_data_on_destroy"]; ok {
featuresMap.RecoveryService.VMBackupStopProtectionAndRetainDataOnDestroy = v.(bool)
}
if v, ok := recoveryServicesRaw["vm_backup_suspend_protection_and_retain_data_on_destroy"]; ok {
featuresMap.RecoveryService.VMBackupSuspendProtectionAndRetainDataOnDestroy = v.(bool)
}
if v, ok := recoveryServicesRaw["purge_protected_items_from_vault_on_destroy"]; ok {
featuresMap.RecoveryService.PurgeProtectedItemsFromVaultOnDestroy = v.(bool)
}
Expand Down
50 changes: 30 additions & 20 deletions internal/provider/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ func TestExpandFeatures(t *testing.T) {
PurgeSoftDeletedWorkspaceOnDestroy: false,
},
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
VMBackupStopProtectionAndRetainDataOnDestroy: false,
VMBackupSuspendProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
},
NetApp: features.NetAppFeatures{
DeleteBackupsOnBackupVaultDestroy: false,
Expand Down Expand Up @@ -201,8 +202,9 @@ func TestExpandFeatures(t *testing.T) {
},
"recovery_service": []interface{}{
map[string]interface{}{
"vm_backup_stop_protection_and_retain_data_on_destroy": true,
"purge_protected_items_from_vault_on_destroy": true,
"vm_backup_stop_protection_and_retain_data_on_destroy": true,
"vm_backup_suspend_protection_and_retain_data_on_destroy": true,
"purge_protected_items_from_vault_on_destroy": true,
},
},
"netapp": []interface{}{
Expand Down Expand Up @@ -281,8 +283,9 @@ func TestExpandFeatures(t *testing.T) {
PurgeSoftDeletedWorkspaceOnDestroy: true,
},
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: true,
PurgeProtectedItemsFromVaultOnDestroy: true,
VMBackupStopProtectionAndRetainDataOnDestroy: true,
VMBackupSuspendProtectionAndRetainDataOnDestroy: true,
PurgeProtectedItemsFromVaultOnDestroy: true,
},
NetApp: features.NetAppFeatures{
DeleteBackupsOnBackupVaultDestroy: true,
Expand Down Expand Up @@ -394,8 +397,9 @@ func TestExpandFeatures(t *testing.T) {
},
"recovery_service": []interface{}{
map[string]interface{}{
"vm_backup_stop_protection_and_retain_data_on_destroy": false,
"purge_protected_items_from_vault_on_destroy": false,
"vm_backup_stop_protection_and_retain_data_on_destroy": false,
"vm_backup_suspend_protection_and_retain_data_on_destroy": false,
"purge_protected_items_from_vault_on_destroy": false,
},
},
"netapp": []interface{}{
Expand Down Expand Up @@ -474,8 +478,9 @@ func TestExpandFeatures(t *testing.T) {
PurgeSoftDeletedWorkspaceOnDestroy: false,
},
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
VMBackupStopProtectionAndRetainDataOnDestroy: false,
VMBackupSuspendProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
},
NetApp: features.NetAppFeatures{
DeleteBackupsOnBackupVaultDestroy: false,
Expand Down Expand Up @@ -1716,8 +1721,9 @@ func TestExpandFeaturesRecoveryService(t *testing.T) {
},
Expected: features.UserFeatures{
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
VMBackupStopProtectionAndRetainDataOnDestroy: false,
VMBackupSuspendProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
},
},
},
Expand All @@ -1727,16 +1733,18 @@ func TestExpandFeaturesRecoveryService(t *testing.T) {
map[string]interface{}{
"recovery_service": []interface{}{
map[string]interface{}{
"vm_backup_stop_protection_and_retain_data_on_destroy": true,
"purge_protected_items_from_vault_on_destroy": true,
"vm_backup_stop_protection_and_retain_data_on_destroy": true,
"vm_backup_suspend_protection_and_retain_data_on_destroy": true,
"purge_protected_items_from_vault_on_destroy": true,
},
},
},
},
Expected: features.UserFeatures{
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: true,
PurgeProtectedItemsFromVaultOnDestroy: true,
VMBackupStopProtectionAndRetainDataOnDestroy: true,
VMBackupSuspendProtectionAndRetainDataOnDestroy: true,
PurgeProtectedItemsFromVaultOnDestroy: true,
},
},
},
Expand All @@ -1746,16 +1754,18 @@ func TestExpandFeaturesRecoveryService(t *testing.T) {
map[string]interface{}{
"recovery_service": []interface{}{
map[string]interface{}{
"vm_backup_stop_protection_and_retain_data_on_destroy": false,
"purge_protected_items_from_vault_on_destroy": false,
"vm_backup_stop_protection_and_retain_data_on_destroy": false,
"vm_backup_suspend_protection_and_retain_data_on_destroy": false,
"purge_protected_items_from_vault_on_destroy": false,
},
},
},
},
Expected: features.UserFeatures{
RecoveryService: features.RecoveryServiceFeatures{
VMBackupStopProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
VMBackupStopProtectionAndRetainDataOnDestroy: false,
VMBackupSuspendProtectionAndRetainDataOnDestroy: false,
PurgeProtectedItemsFromVaultOnDestroy: false,
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions internal/provider/framework/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ func (p *ProviderConfig) Load(ctx context.Context, data *ProviderModel, tfVersio
f.RecoveryService.VMBackupStopProtectionAndRetainDataOnDestroy = feature[0].VMBackupStopProtectionAndRetainDataOnDestroy.ValueBool()
}

f.RecoveryService.VMBackupSuspendProtectionAndRetainDataOnDestroy = false
if !feature[0].VMBackupSuspendProtectionAndRetainDataOnDestroy.IsNull() && !feature[0].VMBackupSuspendProtectionAndRetainDataOnDestroy.IsUnknown() {
f.RecoveryService.VMBackupSuspendProtectionAndRetainDataOnDestroy = feature[0].VMBackupSuspendProtectionAndRetainDataOnDestroy.ValueBool()
}

f.RecoveryService.PurgeProtectedItemsFromVaultOnDestroy = false
if !feature[0].PurgeProtectedItemsFromVaultOnDestroy.IsNull() && !feature[0].PurgeProtectedItemsFromVaultOnDestroy.IsUnknown() {
f.RecoveryService.PurgeProtectedItemsFromVaultOnDestroy = feature[0].PurgeProtectedItemsFromVaultOnDestroy.ValueBool()
Expand Down
14 changes: 10 additions & 4 deletions internal/provider/framework/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ func TestProviderConfig_LoadDefault(t *testing.T) {
t.Errorf("expected recver_services.vm_backup_stop_protection_and_retain_data_on_destroy to be false")
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
}

if features.RecoveryService.VMBackupSuspendProtectionAndRetainDataOnDestroy {
t.Errorf("expected recver_services.vm_backup_suspend_protection_and_retain_data_on_destroy to be false")
sreallymatt marked this conversation as resolved.
Show resolved Hide resolved
}

if features.RecoveryService.PurgeProtectedItemsFromVaultOnDestroy {
t.Errorf("expected recovery_service.PurgeProtectedItemsFromVaultOnDestroy to be false")
}
Expand Down Expand Up @@ -306,14 +310,16 @@ func defaultFeaturesList() types.List {
machineLearningList, _ := basetypes.NewListValue(types.ObjectType{}.WithAttributeTypes(MachineLearningAttributes), []attr.Value{machineLearning})

recoveryServices, _ := basetypes.NewObjectValueFrom(context.Background(), RecoveryServiceAttributes, map[string]attr.Value{
"vm_backup_stop_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"purge_protected_items_from_vault_on_destroy": basetypes.NewBoolNull(),
"vm_backup_stop_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"vm_backup_suspend_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"purge_protected_items_from_vault_on_destroy": basetypes.NewBoolNull(),
})
recoveryServicesList, _ := basetypes.NewListValue(types.ObjectType{}.WithAttributeTypes(RecoveryServiceAttributes), []attr.Value{recoveryServices})

recoveryServicesVaults, _ := basetypes.NewObjectValueFrom(context.Background(), RecoveryServiceVaultsAttributes, map[string]attr.Value{
"vm_backup_stop_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"purge_protected_items_from_vault_on_destroy": basetypes.NewBoolNull(),
"vm_backup_stop_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"vm_backup_suspend_protection_and_retain_data_on_destroy": basetypes.NewBoolNull(),
"purge_protected_items_from_vault_on_destroy": basetypes.NewBoolNull(),
})
recoveryServicesVaultsList, _ := basetypes.NewListValue(types.ObjectType{}.WithAttributeTypes(RecoveryServiceVaultsAttributes), []attr.Value{recoveryServicesVaults})

Expand Down
10 changes: 6 additions & 4 deletions internal/provider/framework/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,15 @@ var MachineLearningAttributes = map[string]attr.Type{
}

type RecoveryService struct {
VMBackupStopProtectionAndRetainDataOnDestroy types.Bool `tfsdk:"vm_backup_stop_protection_and_retain_data_on_destroy"`
PurgeProtectedItemsFromVaultOnDestroy types.Bool `tfsdk:"purge_protected_items_from_vault_on_destroy"`
VMBackupStopProtectionAndRetainDataOnDestroy types.Bool `tfsdk:"vm_backup_stop_protection_and_retain_data_on_destroy"`
VMBackupSuspendProtectionAndRetainDataOnDestroy types.Bool `tfsdk:"vm_backup_suspend_protection_and_retain_data_on_destroy"`
PurgeProtectedItemsFromVaultOnDestroy types.Bool `tfsdk:"purge_protected_items_from_vault_on_destroy"`
}

var RecoveryServiceAttributes = map[string]attr.Type{
"vm_backup_stop_protection_and_retain_data_on_destroy": types.BoolType,
"purge_protected_items_from_vault_on_destroy": types.BoolType,
"vm_backup_stop_protection_and_retain_data_on_destroy": types.BoolType,
"vm_backup_suspend_protection_and_retain_data_on_destroy": types.BoolType,
"purge_protected_items_from_vault_on_destroy": types.BoolType,
}

type RecoveryServiceVaults struct {
Expand Down
3 changes: 3 additions & 0 deletions internal/provider/framework/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ func (p *azureRmFrameworkProvider) Schema(_ context.Context, _ provider.SchemaRe
"vm_backup_stop_protection_and_retain_data_on_destroy": schema.BoolAttribute{
Optional: true,
},
"vm_backup_suspend_protection_and_retain_data_on_destroy": schema.BoolAttribute{
Optional: true,
},
"purge_protected_items_from_vault_on_destroy": schema.BoolAttribute{
Optional: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,9 @@ func resourceRecoveryServicesBackupProtectedVMDelete(d *pluginsdk.ResourceData,
return err
}

if meta.(*clients.Client).Features.RecoveryService.VMBackupStopProtectionAndRetainDataOnDestroy {
features := meta.(*clients.Client).Features.RecoveryService

if features.VMBackupStopProtectionAndRetainDataOnDestroy || features.VMBackupSuspendProtectionAndRetainDataOnDestroy {
log.Printf("[DEBUG] Retaining Data and Stopping Protection for %s", id)

existing, err := client.Get(ctx, *id, protecteditems.GetOperationOptions{})
Expand All @@ -370,19 +372,24 @@ func resourceRecoveryServicesBackupProtectedVMDelete(d *pluginsdk.ResourceData,
return fmt.Errorf("making Read request on %s: %+v", id, err)
}

desiredState := protecteditems.ProtectionStateProtectionStopped
if features.VMBackupSuspendProtectionAndRetainDataOnDestroy {
desiredState = protecteditems.ProtectionStateBackupsSuspended
}

if model := existing.Model; model != nil {
if properties := model.Properties; properties != nil {
if vm, ok := properties.(protecteditems.AzureIaaSComputeVMProtectedItem); ok {
updateInput := protecteditems.ProtectedItemResource{
Properties: &protecteditems.AzureIaaSComputeVMProtectedItem{
ProtectionState: pointer.To(protecteditems.ProtectionStateProtectionStopped),
ProtectionState: pointer.To(desiredState),
SourceResourceId: vm.SourceResourceId,
},
}

resp, err := client.CreateOrUpdate(ctx, *id, updateInput)
if err != nil {
return fmt.Errorf("stopping protection and retaining data for %s: %+v", id, err)
return fmt.Errorf("setting protection to %s and retaining data for %s: %+v", desiredState, id, err)
}

operationId, err := parseBackupOperationId(resp.HttpResponse)
Expand Down Expand Up @@ -415,7 +422,7 @@ func resourceRecoveryServicesBackupProtectedVMDelete(d *pluginsdk.ResourceData,
}

if err = resourceRecoveryServicesBackupProtectedVMWaitForDeletion(ctx, client, opResultClient, *id, operationId); err != nil {
return err
return fmt.Errorf("waiting for deletion %s: %+v", id, err)
}

return nil
Expand All @@ -442,8 +449,7 @@ func resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx conte
},
}

_, err := state.WaitForStateContext(ctx)
if err != nil {
if _, err := state.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for %s to provision: %+v", id, err)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,25 @@ func TestAccBackupProtectedVm_protectionStoppedOnDestroy(t *testing.T) {
})
}

func TestAccBackupProtectedVm_protectionSuspendedOnDestroy(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_backup_protected_vm", "test")
r := BackupProtectedVmResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once #27859 is merged, we can update to use a new config here that uses an immutable vault as we can't suspend protection without an immutable vault.

Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("resource_group_name").Exists(),
),
},
data.ImportStep(),
{
Config: r.protectionSuspendOnDestroy(data),
},
})
}

func TestAccBackupProtectedVm_recoverSoftDeletedVM(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_backup_protected_vm", "test")
r := BackupProtectedVmResource{}
Expand Down Expand Up @@ -1098,6 +1117,21 @@ provider "azurerm" {
`, r.baseWithOutProvider(data))
}

func (r BackupProtectedVmResource) protectionSuspendOnDestroy(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is causing issues with the test

=== CONT  TestAccBackupProtectedVm_protectionSuspendedOnDestroy
    testcase.go:173: Step 4/4 error: Error running pre-apply plan: exit status 1
        Error: Duplicate provider configuration
          on terraform_plugin_test.tf line 41:
          41: provider "azurerm" {
        A default (non-aliased) provider configuration for "azurerm" was already
        given at terraform_plugin_test.tf:31,1-19. If multiple configurations are
        required, set the "alias" argument for alternative configurations.
    panic.go:626: Error retrieving state, there may be dangling resources: exit status 1
        Error: Duplicate provider configuration
          on terraform_plugin_test.tf line 41:
          41: provider "azurerm" {
        A default (non-aliased) provider configuration for "azurerm" was already
        given at terraform_plugin_test.tf:31,1-19. If multiple configurations are
        required, set the "alias" argument for alternative configurations.
--- FAIL: TestAccBackupProtectedVm_protectionSuspendedOnDestroy (272.60s)

features {
recovery_service {
vm_backup_suspend_protection_and_retain_data_on_destroy = true
purge_protected_items_from_vault_on_destroy = true
}
}
}

%s
`, r.baseWithOutProvider(data))
}

func (r BackupProtectedVmResource) basicWithSoftDelete(data acceptance.TestData, deleted bool) string {
protectedVMBlock := `
resource "azurerm_backup_protected_vm" "test" {
Expand Down
7 changes: 5 additions & 2 deletions website/docs/guides/features-block.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ provider "azurerm" {
}

recovery_service {
vm_backup_stop_protection_and_retain_data_on_destroy = true
purge_protected_items_from_vault_on_destroy = true
vm_backup_stop_protection_and_retain_data_on_destroy = true
vm_backup_suspend_protection_and_retain_data_on_destroy = true
purge_protected_items_from_vault_on_destroy = true
}

resource_group {
Expand Down Expand Up @@ -241,6 +242,8 @@ The `recovery_service` block supports the following:

* `vm_backup_stop_protection_and_retain_data_on_destroy` - (Optional) Should we retain the data and stop protection instead of destroying the backup protected vm? Defaults to `false`.

* `vm_backup_suspend_protection_and_retain_data_on_destroy` - (Optional) Should we retain the data and suspend protection instead of destroying the backup protected vm? Defaults to `false`.

* `purge_protected_items_from_vault_on_destroy` - (Optional) Should we purge all protected items when destroying the vault. Defaults to `false`.

---
Expand Down
Loading