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: support protection_state #20608

Merged
merged 12 commits into from
Jun 13, 2023
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 {
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
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