Skip to content

Commit

Permalink
azurerm_backup_protected_vm: support protection_state (#20608)
Browse files Browse the repository at this point in the history
  • Loading branch information
ziyeqf authored Jun 13, 2023
1 parent 5f96839 commit 1ed1fed
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 16 deletions.
90 changes: 75 additions & 15 deletions internal/services/recoveryservices/backup_protected_vm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicesbackup/2023-02-01/protecteditems"
"github.com/hashicorp/go-azure-sdk/resource-manager/recoveryservicesbackup/2023-02-01/protectionpolicies"
"github.com/hashicorp/terraform-provider-azurerm/helpers/azure"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
Expand Down Expand Up @@ -72,9 +73,14 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource
return fmt.Errorf("`source_vm_id` must be specified when creating")
}
}

vmId := d.Get("source_vm_id").(string)
policyId := d.Get("backup_policy_id").(string)

if d.IsNewResource() && policyId == "" {
return fmt.Errorf("`backup_policy_id` must be specified during creation")
}

// get VM name from id
parsedVmId, err := vmParse.VirtualMachineID(vmId)
if err != nil {
Expand Down Expand Up @@ -111,21 +117,54 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource
},
}

resp, err := client.CreateOrUpdate(ctx, id, item)
if err != nil {
return fmt.Errorf("creating/updating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}
protectionState, ok := d.GetOk("protection_state")
protectionStopped := strings.EqualFold(protectionState.(string), string(protecteditems.ProtectionStateProtectionStopped))
requireUpdateProtectionState := ok && protectionStopped
skipNormalUpdate := protectionStopped && !d.IsNewResource()

operationId, err := parseBackupOperationId(resp.HttpResponse)
if err != nil {
return fmt.Errorf("issuing creating/updating request for %s: %+v", id, err)
}
// stopped protected item has no `backup_policy_id`, though we can update it before stopping we can not read it.
if !skipNormalUpdate {
resp, err := client.CreateOrUpdate(ctx, id, item)
if err != nil {
return fmt.Errorf("creating/updating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}

if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil {
return err
operationId, err := parseBackupOperationId(resp.HttpResponse)
if err != nil {
return fmt.Errorf("issuing creating/updating request for %s: %+v", id, err)
}

if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil {
return err
}

d.SetId(id.ID())
}

d.SetId(id.ID())
// the protection state will be updated in the additional update.
if requireUpdateProtectionState {
p := protecteditems.ProtectionState(protectionState.(string))
updateInput := protecteditems.ProtectedItemResource{
Properties: &protecteditems.AzureIaaSComputeVMProtectedItem{
ProtectionState: &p,
SourceResourceId: utils.String(vmId),
},
}

resp, err := client.CreateOrUpdate(ctx, id, updateInput)
if err != nil {
return fmt.Errorf("creating/updating %s: %+v", id, err)
}

operationId, err := parseBackupOperationId(resp.HttpResponse)
if err != nil {
return fmt.Errorf("issuing creating/updating request for %s: %+v", id, err)
}

if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil {
return err
}
}

return resourceRecoveryServicesBackupProtectedVMRead(d, meta)
}
Expand Down Expand Up @@ -159,10 +198,17 @@ func resourceRecoveryServicesBackupProtectedVMRead(d *pluginsdk.ResourceData, me
if properties := model.Properties; properties != nil {
if vm, ok := properties.(protecteditems.AzureIaaSComputeVMProtectedItem); ok {
d.Set("source_vm_id", vm.SourceResourceId)
d.Set("protection_state", pointer.From(vm.ProtectionState))

if v := vm.PolicyId; v != nil {
d.Set("backup_policy_id", strings.Replace(*v, "Subscriptions", "subscriptions", 1))
backupPolicyId := ""
if policyId := pointer.From(vm.PolicyId); policyId != "" {
parsedPolicyId, err := protectionpolicies.ParseBackupPolicyIDInsensitively(policyId)
if err != nil {
return fmt.Errorf("parsing policy ID %q: %+v", policyId, err)
}
backupPolicyId = parsedPolicyId.ID()
}
d.Set("backup_policy_id", backupPolicyId)

if v := vm.ExtendedProperties; v != nil && v.DiskExclusionProperties != nil {
if *v.DiskExclusionProperties.IsInclusionList {
Expand Down Expand Up @@ -394,8 +440,8 @@ func resourceRecoveryServicesBackupProtectedVMSchema() map[string]*pluginsdk.Sch

"backup_policy_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: azure.ValidateResourceID,
Optional: true,
ValidateFunc: protectionpolicies.ValidateBackupPolicyID,
},

"exclude_disk_luns": {
Expand All @@ -417,5 +463,19 @@ func resourceRecoveryServicesBackupProtectedVMSchema() map[string]*pluginsdk.Sch
ValidateFunc: validation.IntAtLeast(0),
},
},

"protection_state": {
Type: pluginsdk.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{
string(backup.ProtectedItemStateIRPending),
string(backup.ProtectedItemStateProtected),
string(backup.ProtectedItemStateProtectionError),
string(backup.ProtectedItemStateProtectionStopped),
string(backup.ProtectedItemStateProtectionPaused),
string(backup.ProtectionStateInvalid),
}, false),
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,34 @@ func TestAccBackupProtectedVm_removeVM(t *testing.T) {
})
}

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

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("resource_group_name").Exists(),
),
},
data.ImportStep(),
{
Config: r.protectionStopped(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("resource_group_name").Exists(),
),
},
data.ImportStep(),
{
// vault cannot be deleted unless we unregister all backups
Config: r.base(data),
},
})
}

func (t BackupProtectedVmResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := protecteditems.ParseProtectedItemID(state.ID)
if err != nil {
Expand Down Expand Up @@ -333,6 +361,7 @@ resource "azurerm_virtual_machine" "test" {
enabled = true
storage_uri = azurerm_storage_account.test.primary_blob_endpoint
}
}
resource "azurerm_recovery_services_vault" "test" {
Expand Down Expand Up @@ -687,3 +716,18 @@ resource "azurerm_backup_protected_vm" "test" {
}
`, r.additionalVault(data))
}

func (r BackupProtectedVmResource) protectionStopped(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_backup_protected_vm" "test" {
resource_group_name = azurerm_resource_group.test.name
recovery_vault_name = azurerm_recovery_services_vault.test.name
source_vm_id = azurerm_virtual_machine.test.id
include_disk_luns = [0]
protection_state = "ProtectionStopped"
}
`, r.base(data))
}
4 changes: 3 additions & 1 deletion website/docs/r/backup_protected_vm.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ The following arguments are supported:
~> **NOTE:** After creation, the `source_vm_id` property can be removed without forcing a new resource to be created; however, setting it to a different ID will create a new resource.
This allows the source vm to be deleted without having to remove the backup.

* `backup_policy_id` - (Required) Specifies the id of the backup policy to use.
* `backup_policy_id` - (Optional) Specifies the id of the backup policy to use. Required in creation or when `protection_stopped` is not specified.

* `exclude_disk_luns` - (Optional) A list of Disks' Logical Unit Numbers(LUN) to be excluded for VM Protection.

* `include_disk_luns` - (Optional) A list of Disks' Logical Unit Numbers(LUN) to be included for VM Protection.

* `protection_state` - (Optional) Specifies Protection state of the backup. Possible values are `Invalid`, `IRPending`, `Protected`, `ProtectionStopped`, `ProtectionError` and `ProtectionPaused`.

## Attributes Reference

In addition to the Arguments listed above - the following Attributes are exported:
Expand Down

0 comments on commit 1ed1fed

Please sign in to comment.