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 @@ -73,6 +73,10 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource
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 @@ -112,12 +116,80 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource
if _, err = client.CreateOrUpdate(ctx, id, item); 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(backup.ProtectionStateProtectionStopped))
requireAdditionalUpdate := ok && protectionStopped
skipNormalUpdate := protectionStopped && !d.IsNewResource()

if !skipNormalUpdate {
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
if _, err = client.CreateOrUpdate(ctx, id, item); err != nil {
return fmt.Errorf("creating/updating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}

if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, client, id, d); err != nil {
return err
if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, client, id, d); err != nil {
return err
}
err := resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, client, id, d)
if err != nil {
return err
}

d.SetId(id.ID())
d.SetId(id.ID())
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
}

d.SetId(id.ID())
if requireAdditionalUpdate {
ziyeqf marked this conversation as resolved.
Show resolved Hide resolved
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)
}

// tracked on https://github.com/Azure/azure-rest-api-specs/issues/22758
locationURL, err := resp.HttpResponse.Location()
if err != nil || locationURL == nil {
return fmt.Errorf("creating/updating %s: Location header missing or empty", id)
}

parsedLocation, err := azure.ParseAzureResourceID(handleAzureSdkForGoBug2824(locationURL.Path))
if err != nil {
return err
}

deadline, ok := ctx.Deadline()
if !ok {
return fmt.Errorf("internal-error: context had no deadline")
}

opResultClient := meta.(*clients.Client).RecoveryServices.BackupOperationResultsClient
operationId := parsedLocation.Path["operationResults"]
opState := &pluginsdk.StateChangeConf{
MinTimeout: 30 * time.Second,
Delay: 10 * time.Second,
Pending: []string{"202"},
Target: []string{"200", "204"},
Refresh: func() (interface{}, string, error) {
resp, err := opResultClient.Get(ctx, id.VaultName, id.ResourceGroupName, operationId)
if err != nil {
return nil, "Error", fmt.Errorf("making Read request on Recovery Service Protected Item operation %q for %s: %+v", operationId, id, err)
}
return resp, strconv.Itoa(resp.StatusCode), err
},
Timeout: time.Until(deadline),
}

if _, err := opState.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("creating/updating %s: %+v", id, err)
}

}

return resourceRecoveryServicesBackupProtectedVMRead(d, meta)
}
Expand Down Expand Up @@ -151,6 +223,7 @@ 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", vm.ProtectionState)

if v := vm.PolicyId; v != nil {
d.Set("backup_policy_id", strings.Replace(*v, "Subscriptions", "subscriptions", 1))
Expand Down Expand Up @@ -368,8 +441,8 @@ func resourceRecoveryServicesBackupProtectedVMSchema() map[string]*pluginsdk.Sch

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

"exclude_disk_luns": {
Expand All @@ -391,5 +464,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.protectionState(data, "ProtectionStopped"),
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) protectionState(data acceptance.TestData, state string) 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 = "%s"
}
`, r.base(data), state)
}
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 @@ -62,12 +62,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