diff --git a/internal/services/recoveryservices/backup_policy_vm_resource.go b/internal/services/recoveryservices/backup_policy_vm_resource.go index bdfd54eedcf0..1b39e30a01c2 100644 --- a/internal/services/recoveryservices/backup_policy_vm_resource.go +++ b/internal/services/recoveryservices/backup_policy_vm_resource.go @@ -149,6 +149,7 @@ func resourceBackupProtectionPolicyVMCreateUpdate(d *pluginsdk.ResourceData, met TimeZone: utils.String(d.Get("timezone").(string)), PolicyType: pointer.To(policyType), SchedulePolicy: schedulePolicy, + TieringPolicy: expandBackupProtectionPolicyVMTieringPolicy(d.Get("tiering_policy").([]interface{})), InstantRPDetails: expandBackupProtectionPolicyVMResourceGroup(d), RetentionPolicy: &protectionpolicies.LongTermRetentionPolicy{ // SimpleRetentionPolicy only has duration property ¯\_(ツ)_/¯ DailySchedule: expandBackupProtectionPolicyVMRetentionDaily(d, times), @@ -214,6 +215,7 @@ func resourceBackupProtectionPolicyVMRead(d *pluginsdk.ResourceData, meta interf if properties, ok := model.Properties.(protectionpolicies.AzureIaaSVMProtectionPolicy); ok { d.Set("timezone", properties.TimeZone) d.Set("instant_restore_retention_days", properties.InstantRpRetentionRangeInDays) + d.Set("tiering_policy", flattenBackupProtectionPolicyVMTieringPolicy(properties.TieringPolicy)) if schedule, ok := properties.SchedulePolicy.(protectionpolicies.SimpleSchedulePolicy); ok { if err := d.Set("backup", flattenBackupProtectionPolicyVMSchedule(schedule)); err != nil { @@ -564,6 +566,47 @@ func expandBackupProtectionPolicyVMRetentionDailyFormat(block map[string]interfa return &daily } +func expandBackupProtectionPolicyVMTieringPolicy(input []interface{}) *map[string]protectionpolicies.TieringPolicy { + result := make(map[string]protectionpolicies.TieringPolicy) + if len(input) == 0 { + result["ArchivedRP"] = protectionpolicies.TieringPolicy{ + TieringMode: pointer.To(protectionpolicies.TieringModeDoNotTier), + DurationType: pointer.To(protectionpolicies.RetentionDurationTypeInvalid), + Duration: pointer.To(int64(0)), + } + + return &result + } + + tieringPolicy := input[0].(map[string]interface{}) + archivedRP := tieringPolicy["archived_restore_point"].([]interface{}) + result["ArchivedRP"] = expandBackupProtectionPolicyVMArchivedRP(archivedRP) + + return &result +} + +func expandBackupProtectionPolicyVMArchivedRP(input []interface{}) protectionpolicies.TieringPolicy { + if len(input) == 0 { + return protectionpolicies.TieringPolicy{} + } + + archivedRP := input[0].(map[string]interface{}) + + result := protectionpolicies.TieringPolicy{ + TieringMode: pointer.To(protectionpolicies.TieringMode(archivedRP["mode"].(string))), + } + + if v := archivedRP["duration_type"].(string); v != "" { + result.DurationType = pointer.To(protectionpolicies.RetentionDurationType(v)) + } + + if v := archivedRP["duration"].(int); v != 0 { + result.Duration = pointer.To(int64(v)) + } + + return result +} + func flattenBackupProtectionPolicyVMResourceGroup(rpDetail protectionpolicies.InstantRPAdditionalDetails) []interface{} { if rpDetail.AzureBackupRGNamePrefix == nil { return nil @@ -769,6 +812,46 @@ func flattenBackupProtectionPolicyVMRetentionDailyFormat(retention *protectionpo return days, includeLastDay } +func flattenBackupProtectionPolicyVMTieringPolicy(input *map[string]protectionpolicies.TieringPolicy) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for k, v := range *input { + if k == "ArchivedRP" { + if pointer.From(v.TieringMode) == protectionpolicies.TieringModeDoNotTier && pointer.From(v.DurationType) == protectionpolicies.RetentionDurationTypeInvalid && pointer.From(v.Duration) == 0 { + return results + } + + results = append(results, map[string]interface{}{ + "archived_restore_point": flattenBackupProtectionPolicyVMArchivedRP(v), + }) + } + } + + return results +} + +func flattenBackupProtectionPolicyVMArchivedRP(input protectionpolicies.TieringPolicy) []interface{} { + results := make([]interface{}, 0) + + result := map[string]interface{}{ + "mode": string(pointer.From(input.TieringMode)), + "duration": int(pointer.From(input.Duration)), + } + + durationType := "" + if v := input.DurationType; v != nil && pointer.From(v) != protectionpolicies.RetentionDurationTypeInvalid { + durationType = string(pointer.From(v)) + } + result["duration_type"] = durationType + + results = append(results, result) + + return results +} + func resourceBackupProtectionPolicyVMWaitForUpdate(ctx context.Context, client *protectionpolicies.ProtectionPoliciesClient, id protectionpolicies.BackupPolicyId, d *pluginsdk.ResourceData) error { state := &pluginsdk.StateChangeConf{ MinTimeout: 30 * time.Second, @@ -853,6 +936,51 @@ func resourceBackupProtectionPolicyVMSchema() map[string]*pluginsdk.Schema { ValidateFunc: validate.RecoveryServicesVaultName, }, + // `tiering_policy` is defined as the map type in Swagger and currently the key only supports `ArchivedRP`. Service team confirmed that they would support other keys in the future. + "tiering_policy": { + Type: pluginsdk.TypeList, + MaxItems: 1, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "archived_restore_point": { + Type: pluginsdk.TypeList, + MaxItems: 1, + Required: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "mode": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(protectionpolicies.TieringModeTierAfter), + string(protectionpolicies.TieringModeTierRecommended), + }, false), + }, + + "duration": { + Type: pluginsdk.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(3), + }, + + "duration_type": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(protectionpolicies.RetentionDurationTypeDays), + string(protectionpolicies.RetentionDurationTypeWeeks), + string(protectionpolicies.RetentionDurationTypeMonths), + string(protectionpolicies.RetentionDurationTypeYears), + }, false), + }, + }, + }, + }, + }, + }, + }, + "timezone": { Type: pluginsdk.TypeString, Optional: true, diff --git a/internal/services/recoveryservices/backup_policy_vm_resource_test.go b/internal/services/recoveryservices/backup_policy_vm_resource_test.go index 49ce769e615a..3915bb004a43 100644 --- a/internal/services/recoveryservices/backup_policy_vm_resource_test.go +++ b/internal/services/recoveryservices/backup_policy_vm_resource_test.go @@ -578,6 +578,21 @@ func TestAccBackupProtectionPolicyVM_completeDays(t *testing.T) { }) } +func TestAccBackupProtectionPolicyVM_tieringPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_backup_policy_vm", "test") + r := BackupProtectionPolicyVMResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.tieringPolicy(data), + Check: acceptance.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (t BackupProtectionPolicyVMResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := protectionpolicies.ParseBackupPolicyID(state.ID) if err != nil { @@ -1007,3 +1022,47 @@ resource "azurerm_backup_policy_vm" "test" { } `, r.template(data), data.RandomInteger) } + +func (r BackupProtectionPolicyVMResource) tieringPolicy(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-bpvm-%d" + resource_group_name = azurerm_resource_group.test.name + recovery_vault_name = azurerm_recovery_services_vault.test.name + + backup { + frequency = "Weekly" + time = "23:00" + weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"] + } + + retention_weekly { + count = 42 + weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"] + } + + retention_monthly { + count = 7 + weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"] + weeks = ["First", "Last"] + } + + retention_yearly { + count = 77 + weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"] + weeks = ["First", "Last"] + months = ["January", "July"] + } + + tiering_policy { + archived_restore_point { + duration = 5 + duration_type = "Months" + mode = "TierAfter" + } + } +} +`, r.template(data), data.RandomInteger) +} diff --git a/website/docs/r/backup_policy_vm.html.markdown b/website/docs/r/backup_policy_vm.html.markdown index 108b2c0f3979..bb4b16219075 100644 --- a/website/docs/r/backup_policy_vm.html.markdown +++ b/website/docs/r/backup_policy_vm.html.markdown @@ -91,6 +91,8 @@ The following arguments are supported: * `retention_yearly` - (Optional) Configures the policy yearly retention as documented in the `retention_yearly` block below. +* `tiering_policy` - (Optional) A `tiering_policy` block as defined below. + --- The `backup` block supports: @@ -166,6 +168,20 @@ The `retention_yearly` block supports: --- +A `tiering_policy` block supports the following: + +* `archived_restore_point` - (Required) An `archived_restore_point` block as defined below. + +--- + +An `archived_restore_point` block supports the following: + +* `mode` - (Required) The tiering mode to control automatic tiering of recovery points. Possible values are `TierAfter` and `TierRecommended`. + +* `duration` - (Optional) The number of days/weeks/months/years to retain backups in current tier before tiering. + +* `duration_type` - (Optional) The retention duration type. Possible values are `Days`, `Weeks`, `Months` and `Years`. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: