diff --git a/azurerm/provider.go b/azurerm/provider.go index 604329d73c15..7cbe3d9ae42b 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -211,6 +211,8 @@ func Provider() terraform.ResourceProvider { "azurerm_azuread_application": resourceArmActiveDirectoryApplication(), "azurerm_azuread_service_principal_password": resourceArmActiveDirectoryServicePrincipalPassword(), "azurerm_azuread_service_principal": resourceArmActiveDirectoryServicePrincipal(), + "azurerm_backup_protected_vm": resourceArmRecoveryServicesBackupProtectedVM(), + "azurerm_backup_policy_vm": resourceArmBackupProtectionPolicyVM(), "azurerm_bastion_host": resourceArmBastionHost(), "azurerm_batch_account": resourceArmBatchAccount(), "azurerm_batch_application": resourceArmBatchApplication(), @@ -450,6 +452,12 @@ func Provider() terraform.ResourceProvider { "azurerm_shared_image_version": resourceArmSharedImageVersion(), "azurerm_shared_image": resourceArmSharedImage(), "azurerm_signalr_service": resourceArmSignalRService(), + "azurerm_site_recovery_fabric": resourceArmSiteRecoveryFabric(), + "azurerm_site_recovery_network_mapping": resourceArmSiteRecoveryNetworkMapping(), + "azurerm_site_recovery_protection_container": resourceArmSiteRecoveryProtectionContainer(), + "azurerm_site_recovery_protection_container_mapping": resourceArmSiteRecoveryProtectionContainerMapping(), + "azurerm_site_recovery_replicated_vm": resourceArmSiteRecoveryReplicatedVM(), + "azurerm_site_recovery_replication_policy": resourceArmSiteRecoveryReplicationPolicy(), "azurerm_snapshot": resourceArmSnapshot(), "azurerm_sql_active_directory_administrator": resourceArmSqlAdministrator(), "azurerm_sql_database": resourceArmSqlDatabase(), diff --git a/azurerm/resource_arm_backup_policy_vm.go b/azurerm/resource_arm_backup_policy_vm.go new file mode 100644 index 000000000000..c72ab40ac9bd --- /dev/null +++ b/azurerm/resource_arm_backup_policy_vm.go @@ -0,0 +1,771 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2017-07-01/backup" + "github.com/Azure/go-autorest/autorest/date" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmBackupProtectionPolicyVM() *schema.Resource { + return &schema.Resource{ + Create: resourceArmBackupProtectionPolicyVMCreateUpdate, + Read: resourceArmBackupProtectionPolicyVMRead, + Update: resourceArmBackupProtectionPolicyVMCreateUpdate, + Delete: resourceArmBackupProtectionPolicyVMDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[a-zA-Z][-_!a-zA-Z0-9]{2,149}$"), + "Backup Policy name must be 3 - 150 characters long, start with a letter, contain only letters and numbers.", + ), + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + + "timezone": { + Type: schema.TypeString, + Optional: true, + Default: "UTC", + }, + + "backup": { + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + + "frequency": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(backup.ScheduleRunTypeDaily), + string(backup.ScheduleRunTypeWeekly), + }, true), + }, + + "time": { //applies to all backup schedules & retention times (they all must be the same) + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^([01][0-9]|[2][0-3]):([03][0])$"), //time must be on the hour or half past + "Time of day must match the format HH:mm where HH is 00-23 and mm is 00 or 30", + ), + }, + + "weekdays": { //only for weekly + Type: schema.TypeSet, + Optional: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.DayOfTheWeek(true), + }, + }, + }, + }, + }, + + "retention_daily": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 9999), + }, + }, + }, + }, + + "retention_weekly": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 9999), + }, + + "weekdays": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.DayOfTheWeek(true), + }, + }, + }, + }, + }, + + "retention_monthly": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 9999), + }, + + "weeks": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(backup.WeekOfMonthFirst), + string(backup.WeekOfMonthSecond), + string(backup.WeekOfMonthThird), + string(backup.WeekOfMonthFourth), + string(backup.WeekOfMonthLast), + }, true), + }, + }, + + "weekdays": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.DayOfTheWeek(true), + }, + }, + }, + }, + }, + + "retention_yearly": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "count": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 9999), + }, + + "months": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.Month(true), + }, + }, + + "weeks": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validation.StringInSlice([]string{ + string(backup.WeekOfMonthFirst), + string(backup.WeekOfMonthSecond), + string(backup.WeekOfMonthThird), + string(backup.WeekOfMonthFourth), + string(backup.WeekOfMonthLast), + }, true), + }, + }, + + "weekdays": { + Type: schema.TypeSet, + Required: true, + Set: set.HashStringIgnoreCase, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: suppress.CaseDifference, + ValidateFunc: validate.DayOfTheWeek(true), + }, + }, + }, + }, + }, + + "tags": tags.Schema(), + }, + + //if daily, we need daily retention + //if weekly daily cannot be set, and we need weekly + CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error { + _, hasDaily := diff.GetOk("retention_daily") + _, hasWeekly := diff.GetOk("retention_weekly") + + frequencyI, _ := diff.GetOk("backup.0.frequency") + frequency := strings.ToLower(frequencyI.(string)) + if frequency == "daily" { + if !hasDaily { + return fmt.Errorf("`retention_daily` must be set when backup.0.frequency is daily") + } + + if _, ok := diff.GetOk("backup.0.weekdays"); ok { + return fmt.Errorf("`backup.0.weekdays` should be not set when backup.0.frequency is daily") + } + } else if frequency == "weekly" { + if hasDaily { + return fmt.Errorf("`retention_daily` must be not set when backup.0.frequency is weekly") + } + if !hasWeekly { + return fmt.Errorf("`retention_weekly` must be set when backup.0.frequency is weekly") + } + } else { + return fmt.Errorf("Unrecognized value for backup.0.frequency") + } + + return nil + }, + } +} + +func resourceArmBackupProtectionPolicyVMCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectionPoliciesClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + policyName := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + t := d.Get("tags").(map[string]interface{}) + + log.Printf("[DEBUG] Creating/updating Azure Backup Protection Policy %s (resource group %q)", policyName, resourceGroup) + + //getting this ready now because its shared between *everything*, time is... complicated for this resource + timeOfDay := d.Get("backup.0.time").(string) + dateOfDay, err := time.Parse(time.RFC3339, fmt.Sprintf("2018-07-30T%s:00Z", timeOfDay)) + if err != nil { + return fmt.Errorf("Error generating time from %q for policy %q (Resource Group %q): %+v", timeOfDay, policyName, resourceGroup, err) + } + times := append(make([]date.Time, 0), date.Time{Time: dateOfDay}) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err2 := client.Get(ctx, vaultName, resourceGroup, policyName) + if err2 != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Azure Backup Protection Policy %q (Resource Group %q): %+v", policyName, resourceGroup, err2) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_backup_policy_vm", *existing.ID) + } + } + + policy := backup.ProtectionPolicyResource{ + Tags: tags.Expand(t), + Properties: &backup.AzureIaaSVMProtectionPolicy{ + TimeZone: utils.String(d.Get("timezone").(string)), + BackupManagementType: backup.BackupManagementTypeAzureIaasVM, + SchedulePolicy: expandArmBackupProtectionPolicyVMSchedule(d, times), + RetentionPolicy: &backup.LongTermRetentionPolicy{ //SimpleRetentionPolicy only has duration property ¯\_(ツ)_/¯ + RetentionPolicyType: backup.RetentionPolicyTypeLongTermRetentionPolicy, + DailySchedule: expandArmBackupProtectionPolicyVMRetentionDaily(d, times), + WeeklySchedule: expandArmBackupProtectionPolicyVMRetentionWeekly(d, times), + MonthlySchedule: expandArmBackupProtectionPolicyVMRetentionMonthly(d, times), + YearlySchedule: expandArmBackupProtectionPolicyVMRetentionYearly(d, times), + }, + }, + } + if _, err = client.CreateOrUpdate(ctx, vaultName, resourceGroup, policyName, policy); err != nil { + return fmt.Errorf("Error creating/updating Azure Backup Protection Policy %q (Resource Group %q): %+v", policyName, resourceGroup, err) + } + + resp, err := resourceArmBackupProtectionPolicyVMWaitForUpdate(ctx, client, vaultName, resourceGroup, policyName, d) + if err != nil { + return err + } + + id := strings.Replace(*resp.ID, "Subscriptions", "subscriptions", 1) + d.SetId(id) + + return resourceArmBackupProtectionPolicyVMRead(d, meta) +} + +func resourceArmBackupProtectionPolicyVMRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectionPoliciesClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + policyName := id.Path["backupPolicies"] + vaultName := id.Path["vaults"] + resourceGroup := id.ResourceGroup + + log.Printf("[DEBUG] Reading Azure Backup Protection Policy %q (resource group %q)", policyName, resourceGroup) + + resp, err := client.Get(ctx, vaultName, resourceGroup, policyName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Azure Backup Protection Policy %q (Resource Group %q): %+v", policyName, resourceGroup, err) + } + + d.Set("name", policyName) + d.Set("resource_group_name", resourceGroup) + d.Set("recovery_vault_name", vaultName) + + if properties, ok := resp.Properties.AsAzureIaaSVMProtectionPolicy(); ok && properties != nil { + d.Set("timezone", properties.TimeZone) + + if schedule, ok := properties.SchedulePolicy.AsSimpleSchedulePolicy(); ok && schedule != nil { + if err := d.Set("backup", flattenArmBackupProtectionPolicyVMSchedule(schedule)); err != nil { + return fmt.Errorf("Error setting `backup`: %+v", err) + } + } + + if retention, ok := properties.RetentionPolicy.AsLongTermRetentionPolicy(); ok && retention != nil { + if s := retention.DailySchedule; s != nil { + if err := d.Set("retention_daily", flattenArmBackupProtectionPolicyVMRetentionDaily(s)); err != nil { + return fmt.Errorf("Error setting `retention_daily`: %+v", err) + } + } else { + d.Set("retention_daily", nil) + } + + if s := retention.WeeklySchedule; s != nil { + if err := d.Set("retention_weekly", flattenArmBackupProtectionPolicyVMRetentionWeekly(s)); err != nil { + return fmt.Errorf("Error setting `retention_weekly`: %+v", err) + } + } else { + d.Set("retention_weekly", nil) + } + + if s := retention.MonthlySchedule; s != nil { + if err := d.Set("retention_monthly", flattenArmBackupProtectionPolicyVMRetentionMonthly(s)); err != nil { + return fmt.Errorf("Error setting `retention_monthly`: %+v", err) + } + } else { + d.Set("retention_monthly", nil) + } + + if s := retention.YearlySchedule; s != nil { + if err := d.Set("retention_yearly", flattenArmBackupProtectionPolicyVMRetentionYearly(s)); err != nil { + return fmt.Errorf("Error setting `retention_yearly`: %+v", err) + } + } else { + d.Set("retention_yearly", nil) + } + } + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmBackupProtectionPolicyVMDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectionPoliciesClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + policyName := id.Path["backupPolicies"] + resourceGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + + log.Printf("[DEBUG] Deleting Azure Backup Protected Item %q (resource group %q)", policyName, resourceGroup) + + resp, err := client.Delete(ctx, vaultName, resourceGroup, policyName) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("Error issuing delete request for Azure Backup Protection Policy %q (Resource Group %q): %+v", policyName, resourceGroup, err) + } + } + + if _, err := resourceArmBackupProtectionPolicyVMWaitForDeletion(ctx, client, vaultName, resourceGroup, policyName, d); err != nil { + return err + } + + return nil +} + +func expandArmBackupProtectionPolicyVMSchedule(d *schema.ResourceData, times []date.Time) *backup.SimpleSchedulePolicy { + if bb, ok := d.Get("backup").([]interface{}); ok && len(bb) > 0 { + block := bb[0].(map[string]interface{}) + + schedule := backup.SimpleSchedulePolicy{ //LongTermSchedulePolicy has no properties + SchedulePolicyType: backup.SchedulePolicyTypeSimpleSchedulePolicy, + ScheduleRunTimes: ×, + } + + if v, ok := block["frequency"].(string); ok { + schedule.ScheduleRunFrequency = backup.ScheduleRunType(v) + } + + if v, ok := block["weekdays"].(*schema.Set); ok { + days := make([]backup.DayOfWeek, 0) + for _, day := range v.List() { + days = append(days, backup.DayOfWeek(day.(string))) + } + schedule.ScheduleRunDays = &days + } + + return &schedule + } + + return nil +} + +func expandArmBackupProtectionPolicyVMRetentionDaily(d *schema.ResourceData, times []date.Time) *backup.DailyRetentionSchedule { + if rb, ok := d.Get("retention_daily").([]interface{}); ok && len(rb) > 0 { + block := rb[0].(map[string]interface{}) + + return &backup.DailyRetentionSchedule{ + RetentionTimes: ×, + RetentionDuration: &backup.RetentionDuration{ + Count: utils.Int32(int32(block["count"].(int))), + DurationType: backup.RetentionDurationTypeDays, + }, + } + } + + return nil +} + +func expandArmBackupProtectionPolicyVMRetentionWeekly(d *schema.ResourceData, times []date.Time) *backup.WeeklyRetentionSchedule { + if rb, ok := d.Get("retention_weekly").([]interface{}); ok && len(rb) > 0 { + block := rb[0].(map[string]interface{}) + + retention := backup.WeeklyRetentionSchedule{ + RetentionTimes: ×, + RetentionDuration: &backup.RetentionDuration{ + Count: utils.Int32(int32(block["count"].(int))), + DurationType: backup.RetentionDurationTypeWeeks, + }, + } + + if v, ok := block["weekdays"].(*schema.Set); ok { + days := make([]backup.DayOfWeek, 0) + for _, day := range v.List() { + days = append(days, backup.DayOfWeek(day.(string))) + } + retention.DaysOfTheWeek = &days + } + + return &retention + } + + return nil +} + +func expandArmBackupProtectionPolicyVMRetentionMonthly(d *schema.ResourceData, times []date.Time) *backup.MonthlyRetentionSchedule { + if rb, ok := d.Get("retention_monthly").([]interface{}); ok && len(rb) > 0 { + block := rb[0].(map[string]interface{}) + + retention := backup.MonthlyRetentionSchedule{ + RetentionScheduleFormatType: backup.RetentionScheduleFormatWeekly, //this is always weekly ¯\_(ツ)_/¯ + RetentionScheduleDaily: nil, //and this is always nil.. + RetentionScheduleWeekly: expandArmBackupProtectionPolicyVMRetentionWeeklyFormat(block), + RetentionTimes: ×, + RetentionDuration: &backup.RetentionDuration{ + Count: utils.Int32(int32(block["count"].(int))), + DurationType: backup.RetentionDurationTypeMonths, + }, + } + + return &retention + } + + return nil +} + +func expandArmBackupProtectionPolicyVMRetentionYearly(d *schema.ResourceData, times []date.Time) *backup.YearlyRetentionSchedule { + if rb, ok := d.Get("retention_yearly").([]interface{}); ok && len(rb) > 0 { + block := rb[0].(map[string]interface{}) + + retention := backup.YearlyRetentionSchedule{ + RetentionScheduleFormatType: backup.RetentionScheduleFormatWeekly, //this is always weekly ¯\_(ツ)_/¯ + RetentionScheduleDaily: nil, //and this is always nil.. + RetentionScheduleWeekly: expandArmBackupProtectionPolicyVMRetentionWeeklyFormat(block), + RetentionTimes: ×, + RetentionDuration: &backup.RetentionDuration{ + Count: utils.Int32(int32(block["count"].(int))), + DurationType: backup.RetentionDurationTypeYears, + }, + } + + if v, ok := block["months"].(*schema.Set); ok { + months := make([]backup.MonthOfYear, 0) + for _, month := range v.List() { + months = append(months, backup.MonthOfYear(month.(string))) + } + retention.MonthsOfYear = &months + } + + return &retention + } + + return nil +} + +func expandArmBackupProtectionPolicyVMRetentionWeeklyFormat(block map[string]interface{}) *backup.WeeklyRetentionFormat { + weekly := backup.WeeklyRetentionFormat{} + + if v, ok := block["weekdays"].(*schema.Set); ok { + days := make([]backup.DayOfWeek, 0) + for _, day := range v.List() { + days = append(days, backup.DayOfWeek(day.(string))) + } + weekly.DaysOfTheWeek = &days + } + + if v, ok := block["weeks"].(*schema.Set); ok { + weeks := make([]backup.WeekOfMonth, 0) + for _, week := range v.List() { + weeks = append(weeks, backup.WeekOfMonth(week.(string))) + } + weekly.WeeksOfTheMonth = &weeks + } + + return &weekly +} + +func flattenArmBackupProtectionPolicyVMSchedule(schedule *backup.SimpleSchedulePolicy) []interface{} { + block := map[string]interface{}{} + + block["frequency"] = string(schedule.ScheduleRunFrequency) + + if times := schedule.ScheduleRunTimes; times != nil && len(*times) > 0 { + block["time"] = (*times)[0].Format("15:04") + } + + if days := schedule.ScheduleRunDays; days != nil { + weekdays := make([]interface{}, 0) + for _, d := range *days { + weekdays = append(weekdays, string(d)) + } + block["weekdays"] = schema.NewSet(schema.HashString, weekdays) + } + + return []interface{}{block} +} + +func flattenArmBackupProtectionPolicyVMRetentionDaily(daily *backup.DailyRetentionSchedule) []interface{} { + block := map[string]interface{}{} + + if duration := daily.RetentionDuration; duration != nil { + if v := duration.Count; v != nil { + block["count"] = *v + } + } + + return []interface{}{block} +} + +func flattenArmBackupProtectionPolicyVMRetentionWeekly(weekly *backup.WeeklyRetentionSchedule) []interface{} { + block := map[string]interface{}{} + + if duration := weekly.RetentionDuration; duration != nil { + if v := duration.Count; v != nil { + block["count"] = *v + } + } + + if days := weekly.DaysOfTheWeek; days != nil { + weekdays := make([]interface{}, 0) + for _, d := range *days { + weekdays = append(weekdays, string(d)) + } + block["weekdays"] = schema.NewSet(schema.HashString, weekdays) + } + + return []interface{}{block} +} + +func flattenArmBackupProtectionPolicyVMRetentionMonthly(monthly *backup.MonthlyRetentionSchedule) []interface{} { + block := map[string]interface{}{} + + if duration := monthly.RetentionDuration; duration != nil { + if v := duration.Count; v != nil { + block["count"] = *v + } + } + + if weekly := monthly.RetentionScheduleWeekly; weekly != nil { + block["weekdays"], block["weeks"] = flattenArmBackupProtectionPolicyVMRetentionWeeklyFormat(weekly) + } + + return []interface{}{block} +} + +func flattenArmBackupProtectionPolicyVMRetentionYearly(yearly *backup.YearlyRetentionSchedule) []interface{} { + block := map[string]interface{}{} + + if duration := yearly.RetentionDuration; duration != nil { + if v := duration.Count; v != nil { + block["count"] = *v + } + } + + if weekly := yearly.RetentionScheduleWeekly; weekly != nil { + block["weekdays"], block["weeks"] = flattenArmBackupProtectionPolicyVMRetentionWeeklyFormat(weekly) + } + + if months := yearly.MonthsOfYear; months != nil { + slice := make([]interface{}, 0) + for _, d := range *months { + slice = append(slice, string(d)) + } + block["months"] = schema.NewSet(schema.HashString, slice) + } + + return []interface{}{block} +} + +func flattenArmBackupProtectionPolicyVMRetentionWeeklyFormat(retention *backup.WeeklyRetentionFormat) (weekdays, weeks *schema.Set) { + if days := retention.DaysOfTheWeek; days != nil { + slice := make([]interface{}, 0) + for _, d := range *days { + slice = append(slice, string(d)) + } + weekdays = schema.NewSet(schema.HashString, slice) + } + + if days := retention.WeeksOfTheMonth; days != nil { + slice := make([]interface{}, 0) + for _, d := range *days { + slice = append(slice, string(d)) + } + weeks = schema.NewSet(schema.HashString, slice) + } + + return weekdays, weeks +} + +func resourceArmBackupProtectionPolicyVMWaitForUpdate(ctx context.Context, client *backup.ProtectionPoliciesClient, vaultName, resourceGroup, policyName string, d *schema.ResourceData) (backup.ProtectionPolicyResource, error) { + state := &resource.StateChangeConf{ + MinTimeout: 30 * time.Second, + Delay: 10 * time.Second, + Pending: []string{"NotFound"}, + Target: []string{"Found"}, + Refresh: resourceArmBackupProtectionPolicyVMRefreshFunc(ctx, client, vaultName, resourceGroup, policyName), + } + + if features.SupportsCustomTimeouts() { + if d.IsNewResource() { + state.Timeout = d.Timeout(schema.TimeoutCreate) + } else { + state.Timeout = d.Timeout(schema.TimeoutUpdate) + } + } else { + state.Timeout = 30 * time.Minute + } + + resp, err := state.WaitForState() + if err != nil { + return resp.(backup.ProtectionPolicyResource), fmt.Errorf("Error waiting for the Azure Backup Protection Policy %q to be true (Resource Group %q) to provision: %+v", policyName, resourceGroup, err) + } + + return resp.(backup.ProtectionPolicyResource), nil +} + +func resourceArmBackupProtectionPolicyVMWaitForDeletion(ctx context.Context, client *backup.ProtectionPoliciesClient, vaultName, resourceGroup, policyName string, d *schema.ResourceData) (backup.ProtectionPolicyResource, error) { + state := &resource.StateChangeConf{ + MinTimeout: 30 * time.Second, + Delay: 10 * time.Second, + Pending: []string{"Found"}, + Target: []string{"NotFound"}, + Refresh: resourceArmBackupProtectionPolicyVMRefreshFunc(ctx, client, vaultName, resourceGroup, policyName), + } + + if features.SupportsCustomTimeouts() { + state.Timeout = d.Timeout(schema.TimeoutDelete) + } else { + state.Timeout = 30 * time.Minute + } + + resp, err := state.WaitForState() + if err != nil { + return resp.(backup.ProtectionPolicyResource), fmt.Errorf("Error waiting for the Azure Backup Protection Policy %q to be false (Resource Group %q) to provision: %+v", policyName, resourceGroup, err) + } + + return resp.(backup.ProtectionPolicyResource), nil +} + +func resourceArmBackupProtectionPolicyVMRefreshFunc(ctx context.Context, client *backup.ProtectionPoliciesClient, vaultName, resourceGroup, policyName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := client.Get(ctx, vaultName, resourceGroup, policyName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return resp, "NotFound", nil + } + + return resp, "Error", fmt.Errorf("Error making Read request on Azure Backup Protection Policy %q (Resource Group %q): %+v", policyName, resourceGroup, err) + } + + return resp, "Found", nil + } +} diff --git a/azurerm/resource_arm_backup_policy_vm_test.go b/azurerm/resource_arm_backup_policy_vm_test.go new file mode 100644 index 000000000000..21c0644ccbd9 --- /dev/null +++ b/azurerm/resource_arm_backup_policy_vm_test.go @@ -0,0 +1,594 @@ +package azurerm + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMBackupProtectionPolicyVM_basicDaily(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_requiresImport(t *testing.T) { + if !features.ShouldResourcesBeImported() { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_requiresImport(ri, testLocation()), + ExpectError: testRequiresImportError("azurerm_backup_policy_vm"), + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_basicWeekly(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicWeekly(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_completeDaily(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeDaily(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_completeWeekly(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeWeekly(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_updateDaily(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeDaily(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_updateWeekly(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicWeekly(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeWeekly(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_updateDailyToWeekly(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicWeekly(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_updateWeeklyToDaily(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicWeekly(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_basicDaily(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMBackupProtectionPolicyVM_updateWeeklyToPartial(t *testing.T) { + resourceName := "azurerm_backup_policy_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectionPolicyVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeWeekly(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeWeekly(resourceName, ri), + }, + { + Config: testAccAzureRMBackupProtectionPolicyVM_completeWeeklyPartial(ri, testLocation()), + Check: checkAccAzureRMBackupProtectionPolicyVM_completeWeeklyPartial(resourceName, ri), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMBackupProtectionPolicyVmDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ProtectionPoliciesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_backup_policy_vm" { + continue + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + vaultName := rs.Primary.Attributes["recovery_vault_name"] + policyName := rs.Primary.Attributes["name"] + + resp, err := client.Get(ctx, vaultName, resourceGroup, policyName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Recovery Services Vault Policy still exists:\n%#v", resp) + } + + return nil +} + +func testCheckAzureRMBackupProtectionPolicyVmExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ProtectionPoliciesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %q", resourceName) + } + + vaultName := rs.Primary.Attributes["recovery_vault_name"] + policyName := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Recovery Services Vault %q Policy: %q", vaultName, policyName) + } + + resp, err := client.Get(ctx, vaultName, resourceGroup, policyName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Recovery Services Vault Policy %q (resource group: %q) was not found: %+v", policyName, resourceGroup, err) + } + + return fmt.Errorf("Bad: Get on recoveryServicesVaultsClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMBackupProtectionPolicyVM_base(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} +`, rInt, location, strconv.Itoa(rInt)[12:17]) +} + +func testAccAzureRMBackupProtectionPolicyVM_basicDaily(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } +} +`, testAccAzureRMBackupProtectionPolicyVM_base(rInt, location), rInt) +} + +func testAccAzureRMBackupProtectionPolicyVM_requiresImport(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "import" { + name = "${azurerm_backup_policy_vm.test.name}" + resource_group_name = "${azurerm_backup_policy_vm.test.resource_group_name}" + recovery_vault_name = "${azurerm_backup_policy_vm.test.recovery_vault_name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } +} +`, testAccAzureRMBackupProtectionPolicyVM_basicDaily(rInt, location)) +} + +func checkAccAzureRMBackupProtectionPolicyVM_basicDaily(resourceName string, ri int) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMBackupProtectionPolicyVmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "resource_group_name", fmt.Sprintf("acctestRG-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "recovery_vault_name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "backup.0.frequency", "Daily"), + resource.TestCheckResourceAttr(resourceName, "backup.0.time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "retention_daily.0.count", "10"), + ) +} + +func testAccAzureRMBackupProtectionPolicyVM_basicWeekly(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%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"] + } + + retention_weekly { + count = 42 + weekdays = ["Sunday", "Wednesday"] + } +} +`, testAccAzureRMBackupProtectionPolicyVM_base(rInt, location), rInt) +} + +func checkAccAzureRMBackupProtectionPolicyVM_basicWeekly(resourceName string, ri int) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMBackupProtectionPolicyVmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "resource_group_name", fmt.Sprintf("acctestRG-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "recovery_vault_name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "backup.0.frequency", "Weekly"), + resource.TestCheckResourceAttr(resourceName, "backup.0.time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.count", "42"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.weekdays.#", "2"), + ) +} + +func testAccAzureRMBackupProtectionPolicyVM_completeDaily(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } + + retention_weekly { + count = 42 + weekdays = ["Sunday", "Wednesday"] + } + + retention_monthly { + count = 7 + weekdays = ["Sunday", "Wednesday"] + weeks = ["First", "Last"] + } + + retention_yearly { + count = 77 + weekdays = ["Sunday", "Wednesday"] + weeks = ["First", "Last"] + months = ["January", "July"] + } +} +`, testAccAzureRMBackupProtectionPolicyVM_base(rInt, location), rInt) +} + +func checkAccAzureRMBackupProtectionPolicyVM_completeDaily(resourceName string, ri int) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMBackupProtectionPolicyVmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "resource_group_name", fmt.Sprintf("acctestRG-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "recovery_vault_name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "backup.0.frequency", "Daily"), + resource.TestCheckResourceAttr(resourceName, "backup.0.time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "retention_daily.0.count", "10"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.count", "42"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.weekdays.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.count", "7"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weekdays.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weeks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.count", "77"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weekdays.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weeks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.months.#", "2"), + ) +} + +func testAccAzureRMBackupProtectionPolicyVM_completeWeekly(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%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"] + } +} +`, testAccAzureRMBackupProtectionPolicyVM_base(rInt, location), rInt) +} + +func checkAccAzureRMBackupProtectionPolicyVM_completeWeekly(resourceName string, ri int) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMBackupProtectionPolicyVmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "resource_group_name", fmt.Sprintf("acctestRG-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "recovery_vault_name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "backup.0.frequency", "Weekly"), + resource.TestCheckResourceAttr(resourceName, "backup.0.time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "backup.0.weekdays.#", "4"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.count", "42"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.weekdays.#", "4"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.count", "7"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weekdays.#", "4"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weeks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.count", "77"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weekdays.#", "4"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weeks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.months.#", "2"), + ) +} + +func testAccAzureRMBackupProtectionPolicyVM_completeWeeklyPartial(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%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"] + } + + retention_monthly { + count = 7 + weekdays = ["Sunday", "Wednesday"] + weeks = ["First", "Last"] + } + + retention_yearly { + count = 77 + weekdays = ["Sunday"] + weeks = ["Last"] + months = ["January"] + } +} +`, testAccAzureRMBackupProtectionPolicyVM_base(rInt, location), rInt) +} + +func checkAccAzureRMBackupProtectionPolicyVM_completeWeeklyPartial(resourceName string, ri int) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + testCheckAzureRMBackupProtectionPolicyVmExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "resource_group_name", fmt.Sprintf("acctestRG-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "recovery_vault_name", fmt.Sprintf("acctest-%d", ri)), + resource.TestCheckResourceAttr(resourceName, "backup.0.frequency", "Weekly"), + resource.TestCheckResourceAttr(resourceName, "backup.0.time", "23:00"), + resource.TestCheckResourceAttr(resourceName, "backup.0.weekdays.#", "4"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.count", "42"), + resource.TestCheckResourceAttr(resourceName, "retention_weekly.0.weekdays.#", "3"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.count", "7"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weekdays.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_monthly.0.weeks.#", "2"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.count", "77"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weekdays.#", "1"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.weeks.#", "1"), + resource.TestCheckResourceAttr(resourceName, "retention_yearly.0.months.#", "1"), + ) +} diff --git a/azurerm/resource_arm_backup_protected_vm.go b/azurerm/resource_arm_backup_protected_vm.go new file mode 100644 index 000000000000..3e66707160d4 --- /dev/null +++ b/azurerm/resource_arm_backup_protected_vm.go @@ -0,0 +1,290 @@ +package azurerm + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2017-07-01/backup" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmRecoveryServicesBackupProtectedVM() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRecoveryServicesBackupProtectedVMCreateUpdate, + Read: resourceArmRecoveryServicesBackupProtectedVMRead, + Update: resourceArmRecoveryServicesBackupProtectedVMCreateUpdate, + Delete: resourceArmRecoveryServicesBackupProtectedVMDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(80 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(80 * time.Minute), + Delete: schema.DefaultTimeout(80 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + + "source_vm_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "backup_policy_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmRecoveryServicesBackupProtectedVMCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectedItemsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + resourceGroup := d.Get("resource_group_name").(string) + t := d.Get("tags").(map[string]interface{}) + + vaultName := d.Get("recovery_vault_name").(string) + vmId := d.Get("source_vm_id").(string) + policyId := d.Get("backup_policy_id").(string) + + //get VM name from id + parsedVmId, err := azure.ParseAzureResourceID(vmId) + if err != nil { + return fmt.Errorf("[ERROR] Unable to parse source_vm_id '%s': %+v", vmId, err) + } + vmName, hasName := parsedVmId.Path["virtualMachines"] + if !hasName { + return fmt.Errorf("[ERROR] parsed source_vm_id '%s' doesn't contain 'virtualMachines'", vmId) + } + + protectedItemName := fmt.Sprintf("VM;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + containerName := fmt.Sprintf("iaasvmcontainer;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + + log.Printf("[DEBUG] Creating/updating Azure Backup Protected VM %s (resource group %q)", protectedItemName, resourceGroup) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err2 := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "") + if err2 != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err2) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_backup_protected_vm", *existing.ID) + } + } + + item := backup.ProtectedItemResource{ + Tags: tags.Expand(t), + Properties: &backup.AzureIaaSComputeVMProtectedItem{ + PolicyID: &policyId, + ProtectedItemType: backup.ProtectedItemTypeMicrosoftClassicComputevirtualMachines, + WorkloadType: backup.DataSourceTypeVM, + SourceResourceID: utils.String(vmId), + FriendlyName: utils.String(vmName), + VirtualMachineID: utils.String(vmId), + }, + } + + if _, err = client.CreateOrUpdate(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, item); err != nil { + return fmt.Errorf("Error creating/updating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + } + + resp, err := resourceArmRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, client, vaultName, resourceGroup, containerName, protectedItemName, policyId, d) + if err != nil { + return err + } + + id := strings.Replace(*resp.ID, "Subscriptions", "subscriptions", 1) // This code is a workaround for this bug https://github.com/Azure/azure-sdk-for-go/issues/2824 + d.SetId(id) + + return resourceArmRecoveryServicesBackupProtectedVMRead(d, meta) +} + +func resourceArmRecoveryServicesBackupProtectedVMRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectedItemsClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + protectedItemName := id.Path["protectedItems"] + vaultName := id.Path["vaults"] + resourceGroup := id.ResourceGroup + containerName := id.Path["protectionContainers"] + + log.Printf("[DEBUG] Reading Azure Backup Protected VM %q (resource group %q)", protectedItemName, resourceGroup) + + resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + } + + d.Set("resource_group_name", resourceGroup) + d.Set("recovery_vault_name", vaultName) + + if properties := resp.Properties; properties != nil { + if vm, ok := properties.AsAzureIaaSComputeVMProtectedItem(); ok { + d.Set("source_vm_id", vm.SourceResourceID) + + if v := vm.PolicyID; v != nil { + d.Set("backup_policy_id", strings.Replace(*v, "Subscriptions", "subscriptions", 1)) + } + } + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmRecoveryServicesBackupProtectedVMDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).RecoveryServices.ProtectedItemsClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + protectedItemName := id.Path["protectedItems"] + resourceGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + containerName := id.Path["protectionContainers"] + + log.Printf("[DEBUG] Deleting Azure Backup Protected Item %q (resource group %q)", protectedItemName, resourceGroup) + + resp, err := client.Delete(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName) + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("Error issuing delete request for Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + } + } + + if _, err := resourceArmRecoveryServicesBackupProtectedVMWaitForDeletion(ctx, client, vaultName, resourceGroup, containerName, protectedItemName, "", d); err != nil { + return err + } + + return nil +} + +func resourceArmRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx context.Context, client *backup.ProtectedItemsClient, vaultName, resourceGroup, containerName, protectedItemName string, policyId string, d *schema.ResourceData) (backup.ProtectedItemResource, error) { + state := &resource.StateChangeConf{ + MinTimeout: 30 * time.Second, + Delay: 10 * time.Second, + Pending: []string{"NotFound"}, + Target: []string{"Found"}, + Refresh: resourceArmRecoveryServicesBackupProtectedVMRefreshFunc(ctx, client, vaultName, resourceGroup, containerName, protectedItemName, policyId, true), + } + + if features.SupportsCustomTimeouts() { + if d.IsNewResource() { + state.Timeout = d.Timeout(schema.TimeoutCreate) + } else { + state.Timeout = d.Timeout(schema.TimeoutUpdate) + } + } else { + state.Timeout = 30 * time.Minute + } + + resp, err := state.WaitForState() + if err != nil { + i, _ := resp.(backup.ProtectedItemResource) + return i, fmt.Errorf("Error waiting for the Azure Backup Protected VM %q to be true (Resource Group %q) to provision: %+v", protectedItemName, resourceGroup, err) + } + + return resp.(backup.ProtectedItemResource), nil +} + +func resourceArmRecoveryServicesBackupProtectedVMWaitForDeletion(ctx context.Context, client *backup.ProtectedItemsClient, vaultName, resourceGroup, containerName, protectedItemName string, policyId string, d *schema.ResourceData) (backup.ProtectedItemResource, error) { + state := &resource.StateChangeConf{ + MinTimeout: 30 * time.Second, + Delay: 10 * time.Second, + Pending: []string{"Found"}, + Target: []string{"NotFound"}, + Refresh: resourceArmRecoveryServicesBackupProtectedVMRefreshFunc(ctx, client, vaultName, resourceGroup, containerName, protectedItemName, policyId, false), + } + + if features.SupportsCustomTimeouts() { + state.Timeout = d.Timeout(schema.TimeoutDelete) + } else { + state.Timeout = 30 * time.Minute + } + + resp, err := state.WaitForState() + if err != nil { + i, _ := resp.(backup.ProtectedItemResource) + return i, fmt.Errorf("Error waiting for the Azure Backup Protected VM %q to be false (Resource Group %q) to provision: %+v", protectedItemName, resourceGroup, err) + } + + return resp.(backup.ProtectedItemResource), nil +} + +func resourceArmRecoveryServicesBackupProtectedVMRefreshFunc(ctx context.Context, client *backup.ProtectedItemsClient, vaultName, resourceGroup, containerName, protectedItemName string, policyId string, newResource bool) resource.StateRefreshFunc { + // TODO: split this into two functions + return func() (interface{}, string, error) { + resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return resp, "NotFound", nil + } + + return resp, "Error", fmt.Errorf("Error making Read request on Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + } else if !newResource && policyId != "" { + if properties := resp.Properties; properties != nil { + if vm, ok := properties.AsAzureIaaSComputeVMProtectedItem(); ok { + if v := vm.PolicyID; v != nil { + if strings.Replace(*v, "Subscriptions", "subscriptions", 1) != policyId { + return resp, "NotFound", nil + } + } else { + return resp, "Error", fmt.Errorf("Error reading policy ID attribute nil on Azure Backup Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup) + } + } else { + return resp, "Error", fmt.Errorf("Error reading properties on Azure Backup Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup) + } + } else { + return resp, "Error", fmt.Errorf("Error reading properties on empty Azure Backup Protected VM %q (Resource Group %q)", protectedItemName, resourceGroup) + } + } + return resp, "Found", nil + } +} diff --git a/azurerm/resource_arm_backup_protected_vm_test.go b/azurerm/resource_arm_backup_protected_vm_test.go new file mode 100644 index 000000000000..cfb18988eb89 --- /dev/null +++ b/azurerm/resource_arm_backup_protected_vm_test.go @@ -0,0 +1,644 @@ +package azurerm + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMBackupProtectedVm_basic(t *testing.T) { + resourceName := "azurerm_backup_protected_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectedVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectedVm_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBackupProtectedVmExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { //vault cannot be deleted unless we unregister all backups + Config: testAccAzureRMBackupProtectedVm_base(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func TestAccAzureRMBackupProtectedVm_requiresImport(t *testing.T) { + if !features.ShouldResourcesBeImported() { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_backup_protected_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectedVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectedVm_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBackupProtectedVmExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + ), + }, + { + Config: testAccAzureRMBackupProtectedVm_requiresImport(ri, testLocation()), + ExpectError: testRequiresImportError("azurerm_backup_protected_vm"), + }, + { //vault cannot be deleted unless we unregister all backups + Config: testAccAzureRMBackupProtectedVm_base(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func TestAccAzureRMBackupProtectedVm_separateResourceGroups(t *testing.T) { + resourceName := "azurerm_backup_protected_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectedVmDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMBackupProtectedVm_separateResourceGroups(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMBackupProtectedVmExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "resource_group_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { //vault cannot be deleted unless we unregister all backups + Config: testAccAzureRMBackupProtectedVm_additionalVault(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func TestAccAzureRMBackupProtectedVm_updateBackupPolicyId(t *testing.T) { + virtualMachine := "azurerm_virtual_machine.test" + protectedVmResourceName := "azurerm_backup_protected_vm.test" + fBackupPolicyResourceName := "azurerm_backup_policy_vm.test" + sBackupPolicyResourceName := "azurerm_backup_policy_vm.test_change_backup" + + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMBackupProtectedVmDestroy, + Steps: []resource.TestStep{ + { // Create resources and link first backup policy id + ResourceName: fBackupPolicyResourceName, + Config: testAccAzureRMBackupProtectedVm_linkFirstBackupPolicy(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(protectedVmResourceName, "backup_policy_id", fBackupPolicyResourceName, "id"), + ), + }, + { // Modify backup policy id to the second one + // Set Destroy false to prevent error from cleaning up dangling resource + ResourceName: sBackupPolicyResourceName, + Config: testAccAzureRMBackupProtectedVm_linkSecondBackupPolicy(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(protectedVmResourceName, "backup_policy_id", sBackupPolicyResourceName, "id"), + ), + }, + { // Remove backup policy link + // Backup policy link will need to be removed first so the VM's backup policy subsequently reverts to Default + // Azure API is quite sensitive, adding the step to control resource cleanup order + ResourceName: fBackupPolicyResourceName, + Config: testAccAzureRMBackupProtectedVm_withVM(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + { // Then VM can be removed + ResourceName: virtualMachine, + Config: testAccAzureRMBackupProtectedVm_withSecondPolicy(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + { // Remove backup policies and vault + ResourceName: protectedVmResourceName, + Config: testAccAzureRMBackupProtectedVm_basePolicyTest(ri, testLocation()), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func testCheckAzureRMBackupProtectedVmDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_backup_protected_vm" { + continue + } + + resourceGroup := rs.Primary.Attributes["resource_group_name"] + vaultName := rs.Primary.Attributes["recovery_vault_name"] + vmId := rs.Primary.Attributes["source_vm_id"] + + parsedVmId, err := azure.ParseAzureResourceID(vmId) + if err != nil { + return fmt.Errorf("[ERROR] Unable to parse source_vm_id '%s': %+v", vmId, err) + } + vmName, hasName := parsedVmId.Path["virtualMachines"] + if !hasName { + return fmt.Errorf("[ERROR] parsed source_vm_id '%s' doesn't contain 'virtualMachines'", vmId) + } + + protectedItemName := fmt.Sprintf("VM;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + containerName := fmt.Sprintf("iaasvmcontainer;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ProtectedItemsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + + return err + } + + return fmt.Errorf("Recovery Services Protected VM still exists:\n%#v", resp) + } + + return nil +} + +func testCheckAzureRMBackupProtectedVmExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %q", resourceName) + } + + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for Recovery Services Protected VM: %q", resourceName) + } + + vaultName := rs.Primary.Attributes["recovery_vault_name"] + vmId := rs.Primary.Attributes["source_vm_id"] + + //get VM name from id + parsedVmId, err := azure.ParseAzureResourceID(vmId) + if err != nil { + return fmt.Errorf("[ERROR] Unable to parse source_vm_id '%s': %+v", vmId, err) + } + vmName, hasName := parsedVmId.Path["virtualMachines"] + if !hasName { + return fmt.Errorf("[ERROR] parsed source_vm_id '%s' doesn't contain 'virtualMachines'", vmId) + } + + protectedItemName := fmt.Sprintf("VM;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + containerName := fmt.Sprintf("iaasvmcontainer;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroup, vmName) + + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ProtectedItemsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Recovery Services Protected VM %q (resource group: %q) was not found: %+v", protectedItemName, resourceGroup, err) + } + + return fmt.Errorf("Bad: Get on recoveryServicesVaultsClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMBackupProtectedVm_base(rInt int, location string) string { + rstr := strconv.Itoa(rInt) + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "vnet" + location = "${azurerm_resource_group.test.location}" + address_space = ["10.0.0.0/16"] + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctest_subnet" + virtual_network_name = "${azurerm_virtual_network.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_prefix = "10.0.10.0/24" +} + +resource "azurerm_network_interface" "test" { + name = "acctest_nic" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "acctestipconfig" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } +} + +resource "azurerm_public_ip" "test" { + name = "acctest-ip" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + allocation_method = "Dynamic" + domain_name_label = "acctestip%[1]d" +} + +resource "azurerm_storage_account" "test" { + name = "acctest%[3]s" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_managed_disk" "test" { + name = "acctest-datadisk" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_type = "Standard_LRS" + create_option = "Empty" + disk_size_gb = "1023" +} + +resource "azurerm_virtual_machine" "test" { + name = "acctestvm" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + vm_size = "Standard_A0" + network_interface_ids = ["${azurerm_network_interface.test.id}"] + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "acctest-osdisk" + managed_disk_type = "Standard_LRS" + caching = "ReadWrite" + create_option = "FromImage" + } + + storage_data_disk { + name = "acctest-datadisk" + managed_disk_id = "${azurerm_managed_disk.test.id}" + managed_disk_type = "Standard_LRS" + disk_size_gb = "${azurerm_managed_disk.test.disk_size_gb}" + create_option = "Attach" + lun = 0 + } + + os_profile { + computer_name = "acctest" + admin_username = "vmadmin" + admin_password = "Password123!@#" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + boot_diagnostics { + enabled = true + storage_uri = "${azurerm_storage_account.test.primary_blob_endpoint}" + } +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } +} +`, rInt, location, rstr[len(rstr)-5:]) +} + +func testAccAzureRMBackupProtectedVm_basic(rInt int, location 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}" + backup_policy_id = "${azurerm_backup_policy_vm.test.id}" +} +`, testAccAzureRMBackupProtectedVm_base(rInt, location)) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_basePolicyTest(rInt int, location string) string { + rstr := strconv.Itoa(rInt) + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-recovery1-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "vnet" + location = "${azurerm_resource_group.test.location}" + address_space = ["10.0.0.0/16"] + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctest_subnet" + virtual_network_name = "${azurerm_virtual_network.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + address_prefix = "10.0.10.0/24" +} + +resource "azurerm_network_interface" "test" { + name = "acctest_nic" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "acctestipconfig" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } +} + +resource "azurerm_public_ip" "test" { + name = "acctest-ip" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + allocation_method = "Dynamic" + domain_name_label = "acctestip%[1]d" +} + +resource "azurerm_storage_account" "test" { + name = "acctest%[3]s" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_managed_disk" "test" { + name = "acctest-datadisk" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_type = "Standard_LRS" + create_option = "Empty" + disk_size_gb = "1023" +} +`, rInt, location, rstr[len(rstr)-5:]) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_withVault(rInt int, location string) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-%[2]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} +`, testAccAzureRMBackupProtectedVm_basePolicyTest(rInt, location), rInt) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_withFirstPolicy(rInt int, location string) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_backup_policy_vm" "test" { + name = "acctest-%[2]d" + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } +} +`, testAccAzureRMBackupProtectedVm_withVault(rInt, location), rInt) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_withSecondPolicy(rInt int, location string) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_backup_policy_vm" "test_change_backup" { + name = "acctest2-%[2]d" + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 15 + } +} +`, testAccAzureRMBackupProtectedVm_withFirstPolicy(rInt, location), rInt) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_withVM(rInt int, location string) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_virtual_machine" "test" { + name = "acctestvm-%[2]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + vm_size = "Standard_A0" + network_interface_ids = ["${azurerm_network_interface.test.id}"] + delete_os_disk_on_termination = true + + storage_image_reference { + publisher = "Canonical" + offer = "UbuntuServer" + sku = "16.04-LTS" + version = "latest" + } + + storage_os_disk { + name = "acctest-osdisk" + managed_disk_type = "Standard_LRS" + caching = "ReadWrite" + create_option = "FromImage" + } + + storage_data_disk { + name = "acctest-datadisk" + managed_disk_id = "${azurerm_managed_disk.test.id}" + managed_disk_type = "Standard_LRS" + disk_size_gb = "${azurerm_managed_disk.test.disk_size_gb}" + create_option = "Attach" + lun = 0 + } + + os_profile { + computer_name = "acctest" + admin_username = "vmadmin" + admin_password = "Password123!@#" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + boot_diagnostics { + enabled = true + storage_uri = "${azurerm_storage_account.test.primary_blob_endpoint}" + } +} +`, testAccAzureRMBackupProtectedVm_withSecondPolicy(rInt, location), rInt) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_linkFirstBackupPolicy(rInt int, location 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}" + backup_policy_id = "${azurerm_backup_policy_vm.test.id}" +} +`, testAccAzureRMBackupProtectedVm_withVM(rInt, location)) +} + +// For update backup policy id test +func testAccAzureRMBackupProtectedVm_linkSecondBackupPolicy(rInt int, location 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}" + backup_policy_id = "${azurerm_backup_policy_vm.test_change_backup.id}" +} +`, testAccAzureRMBackupProtectedVm_withVM(rInt, location)) +} + +func testAccAzureRMBackupProtectedVm_requiresImport(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_protected_vm" "import" { + resource_group_name = "${azurerm_backup_protected_vm.test.resource_group_name}" + recovery_vault_name = "${azurerm_backup_protected_vm.test.recovery_vault_name}" + source_vm_id = "${azurerm_backup_protected_vm.test.source_vm_id}" + backup_policy_id = "${azurerm_backup_protected_vm.test.backup_policy_id}" +} +`, testAccAzureRMBackupProtectedVm_basic(rInt, location)) +} + +func testAccAzureRMBackupProtectedVm_additionalVault(rInt int, location string) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_resource_group" "test2" { + name = "acctestRG-recovery2-%[2]d" + location = "%[3]s" +} + +resource "azurerm_recovery_services_vault" "test2" { + name = "acctest2-%[2]d" + location = "${azurerm_resource_group.test2.location}" + resource_group_name = "${azurerm_resource_group.test2.name}" + sku = "Standard" +} + +resource "azurerm_backup_policy_vm" "test2" { + name = "acctest2-%[2]d" + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test2.name}" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } +} +`, testAccAzureRMBackupProtectedVm_base(rInt, location), rInt, location) +} + +func testAccAzureRMBackupProtectedVm_separateResourceGroups(rInt int, location string) string { + return fmt.Sprintf(` +%s + +resource "azurerm_backup_protected_vm" "test" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test2.name}" + backup_policy_id = "${azurerm_backup_policy_vm.test2.id}" + source_vm_id = "${azurerm_virtual_machine.test.id}" +} +`, testAccAzureRMBackupProtectedVm_additionalVault(rInt, location)) +} diff --git a/azurerm/resource_arm_recovery_services_fabric.go b/azurerm/resource_arm_recovery_services_fabric.go index 251ba0fb2452..da879afaf56b 100644 --- a/azurerm/resource_arm_recovery_services_fabric.go +++ b/azurerm/resource_arm_recovery_services_fabric.go @@ -16,10 +16,11 @@ import ( func resourceArmRecoveryServicesFabric() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesFabricCreate, - Read: resourceArmRecoveryServicesFabricRead, - Update: nil, - Delete: resourceArmRecoveryServicesFabricDelete, + DeprecationMessage: "`azurerm_recovery_services_fabric` resource is deprecated in favor of `azurerm_site_recovery_fabric` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesFabricCreate, + Read: resourceArmRecoveryServicesFabricRead, + Update: nil, + Delete: resourceArmRecoveryServicesFabricDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, diff --git a/azurerm/resource_arm_recovery_services_network_mapping.go b/azurerm/resource_arm_recovery_services_network_mapping.go index 64f00cdd0726..38e4ea8589b0 100644 --- a/azurerm/resource_arm_recovery_services_network_mapping.go +++ b/azurerm/resource_arm_recovery_services_network_mapping.go @@ -17,9 +17,10 @@ import ( func resourceArmRecoveryServicesNetworkMapping() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryNetworkMappingCreate, - Read: resourceArmRecoveryNetworkMappingRead, - Delete: resourceArmRecoveryNetworkMappingDelete, + DeprecationMessage: "`azurerm_recovery_network_mapping` resource is deprecated in favor of `azurerm_site_recovery_network_mapping` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryNetworkMappingCreate, + Read: resourceArmRecoveryNetworkMappingRead, + Delete: resourceArmRecoveryNetworkMappingDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, diff --git a/azurerm/resource_arm_recovery_services_protected_vm.go b/azurerm/resource_arm_recovery_services_protected_vm.go index 6675e7f6c83c..43877ee29cc1 100644 --- a/azurerm/resource_arm_recovery_services_protected_vm.go +++ b/azurerm/resource_arm_recovery_services_protected_vm.go @@ -20,10 +20,11 @@ import ( func resourceArmRecoveryServicesProtectedVm() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesProtectedVmCreateUpdate, - Read: resourceArmRecoveryServicesProtectedVmRead, - Update: resourceArmRecoveryServicesProtectedVmCreateUpdate, - Delete: resourceArmRecoveryServicesProtectedVmDelete, + DeprecationMessage: "`azurerm_recovery_services_protected_vm` resource is deprecated in favor of `azurerm_backup_protected_vm` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesProtectedVmCreateUpdate, + Read: resourceArmRecoveryServicesProtectedVmRead, + Update: resourceArmRecoveryServicesProtectedVmCreateUpdate, + Delete: resourceArmRecoveryServicesProtectedVmDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, diff --git a/azurerm/resource_arm_recovery_services_protection_container.go b/azurerm/resource_arm_recovery_services_protection_container.go index a3b5b372d65e..4b4fed85fa70 100644 --- a/azurerm/resource_arm_recovery_services_protection_container.go +++ b/azurerm/resource_arm_recovery_services_protection_container.go @@ -16,10 +16,11 @@ import ( func resourceArmRecoveryServicesProtectionContainer() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesProtectionContainerCreate, - Read: resourceArmRecoveryServicesProtectionContainerRead, - Update: nil, - Delete: resourceArmSiteRecoveryProtectionContainerDelete, + DeprecationMessage: "`azurerm_recovery_services_protection_container` resource is deprecated in favor of `azurerm_site_recovery_protection_container` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesProtectionContainerCreate, + Read: resourceArmRecoveryServicesProtectionContainerRead, + Update: nil, + Delete: resourceArmRecoveryServicesProtectionContainerDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -132,7 +133,7 @@ func resourceArmRecoveryServicesProtectionContainerRead(d *schema.ResourceData, return nil } -func resourceArmSiteRecoveryProtectionContainerDelete(d *schema.ResourceData, meta interface{}) error { +func resourceArmRecoveryServicesProtectionContainerDelete(d *schema.ResourceData, meta interface{}) error { id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err diff --git a/azurerm/resource_arm_recovery_services_protection_container_mapping.go b/azurerm/resource_arm_recovery_services_protection_container_mapping.go index 70a690ec37ac..189e26d5b998 100644 --- a/azurerm/resource_arm_recovery_services_protection_container_mapping.go +++ b/azurerm/resource_arm_recovery_services_protection_container_mapping.go @@ -17,10 +17,11 @@ import ( func resourceArmRecoveryServicesProtectionContainerMapping() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesContainerMappingCreate, - Read: resourceArmRecoveryServicesContainerMappingRead, - Update: nil, - Delete: resourceArmSiteRecoveryServicesContainerMappingDelete, + DeprecationMessage: "`azurerm_recovery_services_protection_container_mapping` resource is deprecated in favor of `azurerm_site_recovery_protection_container_mapping` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesContainerMappingCreate, + Read: resourceArmRecoveryServicesContainerMappingRead, + Update: nil, + Delete: resourceArmRecoveryServicesContainerMappingDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -163,7 +164,7 @@ func resourceArmRecoveryServicesContainerMappingRead(d *schema.ResourceData, met return nil } -func resourceArmSiteRecoveryServicesContainerMappingDelete(d *schema.ResourceData, meta interface{}) error { +func resourceArmRecoveryServicesContainerMappingDelete(d *schema.ResourceData, meta interface{}) error { id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err diff --git a/azurerm/resource_arm_recovery_services_protection_policy_vm.go b/azurerm/resource_arm_recovery_services_protection_policy_vm.go index e0705f614001..3b6f4b485bb4 100644 --- a/azurerm/resource_arm_recovery_services_protection_policy_vm.go +++ b/azurerm/resource_arm_recovery_services_protection_policy_vm.go @@ -26,10 +26,11 @@ import ( func resourceArmRecoveryServicesProtectionPolicyVm() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesProtectionPolicyVmCreateUpdate, - Read: resourceArmRecoveryServicesProtectionPolicyVmRead, - Update: resourceArmRecoveryServicesProtectionPolicyVmCreateUpdate, - Delete: resourceArmRecoveryServicesProtectionPolicyVmDelete, + DeprecationMessage: "`azurerm_recovery_services_protection_policy_vm` resource is deprecated in favor of `azurerm_backup_policy_vm` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesProtectionPolicyVmCreateUpdate, + Read: resourceArmRecoveryServicesProtectionPolicyVmRead, + Update: resourceArmRecoveryServicesProtectionPolicyVmCreateUpdate, + Delete: resourceArmRecoveryServicesProtectionPolicyVmDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, diff --git a/azurerm/resource_arm_recovery_services_replicated_vm.go b/azurerm/resource_arm_recovery_services_replicated_vm.go index 3858a38bdfbb..dbc60db22e0e 100644 --- a/azurerm/resource_arm_recovery_services_replicated_vm.go +++ b/azurerm/resource_arm_recovery_services_replicated_vm.go @@ -22,9 +22,10 @@ import ( func resourceArmRecoveryServicesReplicatedVm() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryReplicatedItemCreate, - Read: resourceArmRecoveryReplicatedItemRead, - Delete: resourceArmRecoveryReplicatedItemDelete, + DeprecationMessage: "`azurerm_recovery_replicated_vm` resource is deprecated in favor of `azurerm_site_recovery_replicated_vm` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryReplicatedItemCreate, + Read: resourceArmRecoveryReplicatedItemRead, + Delete: resourceArmRecoveryReplicatedItemDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, diff --git a/azurerm/resource_arm_recovery_services_replication_policy.go b/azurerm/resource_arm_recovery_services_replication_policy.go index bad0861f5f16..837800ed2f86 100644 --- a/azurerm/resource_arm_recovery_services_replication_policy.go +++ b/azurerm/resource_arm_recovery_services_replication_policy.go @@ -17,10 +17,11 @@ import ( func resourceArmRecoveryServicesReplicationPolicy() *schema.Resource { return &schema.Resource{ - Create: resourceArmRecoveryServicesReplicationPolicyCreate, - Read: resourceArmRecoveryServicesReplicationPolicyRead, - Update: resourceArmRecoveryServicesReplicationPolicyUpdate, - Delete: resourceArmSiteRecoveryReplicationPolicyDelete, + DeprecationMessage: "`azurerm_recovery_services_replication_policy` resource is deprecated in favor of `azurerm_site_recovery_replication_policy` and will be removed in v2.0 of the AzureRM Provider", + Create: resourceArmRecoveryServicesReplicationPolicyCreate, + Read: resourceArmRecoveryServicesReplicationPolicyRead, + Update: resourceArmRecoveryServicesReplicationPolicyUpdate, + Delete: resourceArmRecoveryServicesReplicationPolicyDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, @@ -187,7 +188,7 @@ func resourceArmRecoveryServicesReplicationPolicyRead(d *schema.ResourceData, me return nil } -func resourceArmSiteRecoveryReplicationPolicyDelete(d *schema.ResourceData, meta interface{}) error { +func resourceArmRecoveryServicesReplicationPolicyDelete(d *schema.ResourceData, meta interface{}) error { id, err := azure.ParseAzureResourceID(d.Id()) if err != nil { return err diff --git a/azurerm/resource_arm_site_recovery_fabric.go b/azurerm/resource_arm_site_recovery_fabric.go new file mode 100644 index 000000000000..756dfd4b844d --- /dev/null +++ b/azurerm/resource_arm_site_recovery_fabric.go @@ -0,0 +1,162 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryFabric() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryFabricCreate, + Read: resourceArmSiteRecoveryFabricRead, + Update: nil, + Delete: resourceArmSiteRecoveryFabricDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "location": azure.SchemaLocation(), + }, + } +} + +func resourceArmSiteRecoveryFabricCreate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + location := azure.NormalizeLocation(d.Get("location").(string)) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.FabricClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_fabric", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + parameters := siterecovery.FabricCreationInput{ + Properties: &siterecovery.FabricCreationInputProperties{ + CustomDetails: siterecovery.AzureFabricCreationInput{ + InstanceType: "Azure", + Location: &location, + }, + }, + } + + future, err := client.Create(ctx, name, parameters) + if err != nil { + return fmt.Errorf("Error creating site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryFabricRead(d, meta) +} + +func resourceArmSiteRecoveryFabricRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + name := id.Path["replicationFabrics"] + + client := meta.(*ArmClient).RecoveryServices.FabricClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making read request on site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + if props := resp.Properties; props != nil { + if azureDetails, isAzureDetails := props.CustomDetails.AsAzureFabricSpecificDetails(); isAzureDetails { + d.Set("location", azureDetails.Location) + } + } + d.Set("recovery_vault_name", vaultName) + return nil +} + +func resourceArmSiteRecoveryFabricDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + name := id.Path["replicationFabrics"] + + client := meta.(*ArmClient).RecoveryServices.FabricClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + future, err := client.Delete(ctx, name) + if err != nil { + return fmt.Errorf("Error deleting site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery fabric %s (vault %s): %+v", name, vaultName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_site_recovery_fabric_test.go b/azurerm/resource_arm_site_recovery_fabric_test.go new file mode 100644 index 000000000000..517725d02540 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_fabric_test.go @@ -0,0 +1,97 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryFabric_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test" + vaultName := "azurerm_recovery_services_vault.test" + resourceName := "azurerm_site_recovery_fabric.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryFabric_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryFabricExists(resourceGroupName, vaultName, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryFabric_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric-%d" + location = "${azurerm_resource_group.test.location}" +} +`, rInt, location, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryFabricExists(resourceGroupStateName, vaultStateName string, resourceStateName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + fabricState, ok := s.RootModule().Resources[resourceStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + fabricName := fabricState.Primary.Attributes["name"] + + // Ensure fabric exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.FabricClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, fabricName) + if err != nil { + return fmt.Errorf("Bad: Get on fabricClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: fabric: %q does not exist", fabricName) + } + + return nil + } +} diff --git a/azurerm/resource_arm_site_recovery_network_mapping.go b/azurerm/resource_arm_site_recovery_network_mapping.go new file mode 100644 index 000000000000..f6e502cd1359 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_network_mapping.go @@ -0,0 +1,214 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryNetworkMapping() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryNetworkMappingCreate, + Read: resourceArmSiteRecoveryNetworkMappingRead, + Delete: resourceArmSiteRecoveryNetworkMappingDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "source_recovery_fabric_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "target_recovery_fabric_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "source_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + } +} + +func resourceArmSiteRecoveryNetworkMappingCreate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + fabricName := d.Get("source_recovery_fabric_name").(string) + targetFabricName := d.Get("target_recovery_fabric_name").(string) + sourceNetworkId := d.Get("source_network_id").(string) + targetNetworkId := d.Get("target_network_id").(string) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.NetworkMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + //get network name from id + parsedSourceNetworkId, err := azure.ParseAzureResourceID(sourceNetworkId) + if err != nil { + return fmt.Errorf("[ERROR] Unable to parse source_network_id '%s' (network mapping %s): %+v", sourceNetworkId, name, err) + } + sourceNetworkName, hasName := parsedSourceNetworkId.Path["virtualNetworks"] + if !hasName { + sourceNetworkName, hasName = parsedSourceNetworkId.Path["virtualnetworks"] // Handle that different APIs return different ID casings + if !hasName { + return fmt.Errorf("[ERROR] parsed source_network_id '%s' doesn't contain 'virtualnetworks'", parsedSourceNetworkId) + } + } + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, fabricName, sourceNetworkName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_network_mapping", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + var parameters = siterecovery.CreateNetworkMappingInput{ + Properties: &siterecovery.CreateNetworkMappingInputProperties{ + RecoveryNetworkID: &targetNetworkId, + RecoveryFabricName: &targetFabricName, + FabricSpecificDetails: siterecovery.AzureToAzureCreateNetworkMappingInput{ + PrimaryNetworkID: &sourceNetworkId, + }, + }, + } + future, err := client.Create(ctx, fabricName, sourceNetworkName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, fabricName, sourceNetworkName, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryNetworkMappingRead(d, meta) +} + +func resourceArmSiteRecoveryNetworkMappingRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + networkName := id.Path["replicationNetworks"] + name := id.Path["replicationNetworkMappings"] + + client := meta.(*ArmClient).RecoveryServices.NetworkMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, fabricName, networkName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + + d.Set("resource_group_name", resGroup) + d.Set("recovery_vault_name", vaultName) + d.Set("source_recovery_fabric_name", fabricName) + d.Set("name", resp.Name) + if props := resp.Properties; props != nil { + d.Set("source_network_id", props.PrimaryNetworkID) + d.Set("target_network_id", props.RecoveryNetworkID) + + targetFabricId, err := azure.ParseAzureResourceID(azure.HandleAzureSdkForGoBug2824(*resp.Properties.RecoveryFabricArmID)) + if err != nil { + return err + } + d.Set("target_recovery_fabric_name", targetFabricId.Path["replicationFabrics"]) + } + + return nil +} + +func resourceArmSiteRecoveryNetworkMappingDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + networkName := id.Path["replicationNetworks"] + name := id.Path["replicationNetworkMappings"] + + client := meta.(*ArmClient).RecoveryServices.NetworkMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + future, err := client.Delete(ctx, fabricName, networkName, name) + if err != nil { + return fmt.Errorf("Error deleting site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery network mapping %s (vault %s): %+v", name, vaultName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_site_recovery_network_mapping_test.go b/azurerm/resource_arm_site_recovery_network_mapping_test.go new file mode 100644 index 000000000000..16a962d3f3e1 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_network_mapping_test.go @@ -0,0 +1,141 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryNetworkMapping_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test" + vaultName := "azurerm_recovery_services_vault.test" + fabricName := "azurerm_site_recovery_fabric.test1" + networkName := "azurerm_virtual_network.test1" + resourceName := "azurerm_site_recovery_network_mapping.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryNetworkMapping_basic(ri, testLocation(), testAltLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryNetworkMappingExists(resourceGroupName, vaultName, fabricName, networkName, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryNetworkMapping_basic(rInt int, location string, altLocation string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-recovery1-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test1" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric1-%d" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_site_recovery_fabric" "test2" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric2-%d" + location = "%s" + depends_on = ["azurerm_site_recovery_fabric.test1"] +} + +resource "azurerm_virtual_network" "test1" { + name = "network1-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["192.168.1.0/24"] + location = "${azurerm_site_recovery_fabric.test1.location}" +} + +resource "azurerm_virtual_network" "test2" { + name = "network2-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["192.168.2.0/24"] + location = "${azurerm_site_recovery_fabric.test2.location}" +} + +resource "azurerm_site_recovery_network_mapping" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "mapping-%d" + source_recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + target_recovery_fabric_name = "${azurerm_site_recovery_fabric.test2.name}" + source_network_id = "${azurerm_virtual_network.test1.id}" + target_network_id = "${azurerm_virtual_network.test2.id}" +} +`, rInt, location, rInt, rInt, rInt, altLocation, rInt, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryNetworkMappingExists(resourceGroupStateName, vaultStateName string, fabricStateName string, networkStateName string, networkStateMappingName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + fabricState, ok := s.RootModule().Resources[fabricStateName] + if !ok { + return fmt.Errorf("Not found: %s", fabricStateName) + } + networkState, ok := s.RootModule().Resources[networkStateName] + if !ok { + return fmt.Errorf("Not found: %s", fabricStateName) + } + networkMappingState, ok := s.RootModule().Resources[networkStateMappingName] + if !ok { + return fmt.Errorf("Not found: %s", networkStateMappingName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + fabricName := fabricState.Primary.Attributes["name"] + networkName := networkState.Primary.Attributes["name"] + mappingName := networkMappingState.Primary.Attributes["name"] + + // Ensure mapping exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.NetworkMappingClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, fabricName, networkName, mappingName) + if err != nil { + return fmt.Errorf("Bad: Get on networkMappingClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: networkMapping: %q does not exist", mappingName) + } + + return nil + } +} diff --git a/azurerm/resource_arm_site_recovery_protection_container.go b/azurerm/resource_arm_site_recovery_protection_container.go new file mode 100644 index 000000000000..2a9dfeea2c0d --- /dev/null +++ b/azurerm/resource_arm_site_recovery_protection_container.go @@ -0,0 +1,160 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryProtectionContainer() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryProtectionContainerCreate, + Read: resourceArmSiteRecoveryProtectionContainerRead, + Update: nil, + Delete: resourceArmSiteRecoveryProtectionContainerDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "recovery_fabric_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + } +} + +func resourceArmSiteRecoveryProtectionContainerCreate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + fabricName := d.Get("recovery_fabric_name").(string) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.ProtectionContainerClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, fabricName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_recovery_services_protection_container", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + parameters := siterecovery.CreateProtectionContainerInput{ + Properties: &siterecovery.CreateProtectionContainerInputProperties{}, + } + + future, err := client.Create(ctx, fabricName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + + resp, err := client.Get(ctx, fabricName, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryProtectionContainerRead(d, meta) +} + +func resourceArmSiteRecoveryProtectionContainerRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + name := id.Path["replicationProtectionContainers"] + + client := meta.(*ArmClient).RecoveryServices.ProtectionContainerClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, fabricName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("recovery_vault_name", vaultName) + d.Set("recovery_fabric_name", fabricName) + return nil +} + +func resourceArmSiteRecoveryProtectionContainerDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + name := id.Path["replicationProtectionContainers"] + + client := meta.(*ArmClient).RecoveryServices.ProtectionContainerClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + future, err := client.Delete(ctx, fabricName, name) + if err != nil { + return fmt.Errorf("Error deleting site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery protection container %s (fabric %s): %+v", name, fabricName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_site_recovery_protection_container_mapping.go b/azurerm/resource_arm_site_recovery_protection_container_mapping.go new file mode 100644 index 000000000000..1994fe321c1c --- /dev/null +++ b/azurerm/resource_arm_site_recovery_protection_container_mapping.go @@ -0,0 +1,201 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryProtectionContainerMapping() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryContainerMappingCreate, + Read: resourceArmSiteRecoveryContainerMappingRead, + Update: nil, + Delete: resourceArmSiteRecoveryServicesContainerMappingDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "recovery_fabric_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "recovery_replication_policy_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "recovery_source_protection_container_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "recovery_target_protection_container_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + } +} + +func resourceArmSiteRecoveryContainerMappingCreate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + fabricName := d.Get("recovery_fabric_name").(string) + policyId := d.Get("recovery_replication_policy_id").(string) + protectionContainerName := d.Get("recovery_source_protection_container_name").(string) + targetContainerId := d.Get("recovery_target_protection_container_id").(string) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.ContainerMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, fabricName, protectionContainerName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery protection container mapping %s (fabric %s, container %s): %+v", name, fabricName, protectionContainerName, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_protection_container_mapping", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + var parameters = siterecovery.CreateProtectionContainerMappingInput{ + Properties: &siterecovery.CreateProtectionContainerMappingInputProperties{ + TargetProtectionContainerID: &targetContainerId, + PolicyID: &policyId, + ProviderSpecificInput: siterecovery.ReplicationProviderSpecificContainerMappingInput{}, + }, + } + future, err := client.Create(ctx, fabricName, protectionContainerName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, fabricName, protectionContainerName, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryContainerMappingRead(d, meta) +} + +func resourceArmSiteRecoveryContainerMappingRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + protectionContainerName := id.Path["replicationProtectionContainers"] + name := id.Path["replicationProtectionContainerMappings"] + + client := meta.(*ArmClient).RecoveryServices.ContainerMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, fabricName, protectionContainerName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + + d.Set("resource_group_name", resGroup) + d.Set("recovery_vault_name", vaultName) + d.Set("recovery_fabric_name", fabricName) + d.Set("recovery_source_protection_container_name", resp.Properties.SourceProtectionContainerFriendlyName) + d.Set("name", resp.Name) + d.Set("recovery_replication_policy_id", resp.Properties.PolicyID) + d.Set("recovery_target_protection_container_id", resp.Properties.TargetProtectionContainerID) + return nil +} + +func resourceArmSiteRecoveryServicesContainerMappingDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + protectionContainerName := id.Path["replicationProtectionContainers"] + name := id.Path["replicationProtectionContainerMappings"] + instanceType := string(siterecovery.InstanceTypeBasicReplicationProviderSpecificContainerMappingInputInstanceTypeA2A) + + client := meta.(*ArmClient).RecoveryServices.ContainerMappingClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + input := siterecovery.RemoveProtectionContainerMappingInput{ + Properties: &siterecovery.RemoveProtectionContainerMappingInputProperties{ + ProviderSpecificInput: &siterecovery.ReplicationProviderContainerUnmappingInput{ + InstanceType: &instanceType, + }, + }, + } + + future, err := client.Delete(ctx, fabricName, protectionContainerName, name, input) + if err != nil { + return fmt.Errorf("Error deleting site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery protection container mapping %s (vault %s): %+v", name, vaultName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_site_recovery_protection_container_mapping_test.go b/azurerm/resource_arm_site_recovery_protection_container_mapping_test.go new file mode 100644 index 000000000000..588440f1115f --- /dev/null +++ b/azurerm/resource_arm_site_recovery_protection_container_mapping_test.go @@ -0,0 +1,149 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryProtectionContainerMapping_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test1" + vaultName := "azurerm_recovery_services_vault.test" + fabricName := "azurerm_site_recovery_fabric.test1" + protectionContainerName := "azurerm_site_recovery_protection_container.test1" + resourceName := "azurerm_site_recovery_protection_container_mapping.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryProtectionContainerMapping_basic(ri, testLocation(), testAltLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryProtectionContainerMappingExists(resourceGroupName, vaultName, fabricName, protectionContainerName, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryProtectionContainerMapping_basic(rInt int, location string, altLocation string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test1" { + name = "acctestRG-recovery1-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test1.location}" + resource_group_name = "${azurerm_resource_group.test1.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test1" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric1-%d" + location = "${azurerm_resource_group.test1.location}" +} + +resource "azurerm_site_recovery_fabric" "test2" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric2-%d" + location = "%s" + depends_on = ["azurerm_site_recovery_fabric.test1"] +} + +resource "azurerm_site_recovery_protection_container" "test1" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + name = "acctest-protection-cont1-%d" +} + +resource "azurerm_site_recovery_protection_container" "test2" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test2.name}" + name = "acctest-protection-cont2-%d" +} + +resource "azurerm_site_recovery_replication_policy" "test" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-policy-%d" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} + +resource "azurerm_site_recovery_protection_container_mapping" "test" { + resource_group_name = "${azurerm_resource_group.test1.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + recovery_source_protection_container_name = "${azurerm_site_recovery_protection_container.test1.name}" + recovery_target_protection_container_id = "${azurerm_site_recovery_protection_container.test2.id}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.test.id}" + name = "mapping-%d" +} +`, rInt, location, rInt, rInt, rInt, altLocation, rInt, rInt, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryProtectionContainerMappingExists(resourceGroupStateName, vaultStateName string, resourceStateName string, protectionContainerStateName string, protectionContainerStateMappingName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + fabricState, ok := s.RootModule().Resources[resourceStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + protectionContainerState, ok := s.RootModule().Resources[protectionContainerStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + protectionContainerMappingState, ok := s.RootModule().Resources[protectionContainerStateMappingName] + if !ok { + return fmt.Errorf("Not found: %s", protectionContainerStateMappingName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + fabricName := fabricState.Primary.Attributes["name"] + protectionContainerName := protectionContainerState.Primary.Attributes["name"] + mappingName := protectionContainerMappingState.Primary.Attributes["name"] + + // Ensure mapping exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ContainerMappingClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, fabricName, protectionContainerName, mappingName) + if err != nil { + return fmt.Errorf("Bad: Get on fabricClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: fabric: %q does not exist", fabricName) + } + + return nil + } +} diff --git a/azurerm/resource_arm_site_recovery_protection_container_test.go b/azurerm/resource_arm_site_recovery_protection_container_test.go new file mode 100644 index 000000000000..711242ad69e2 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_protection_container_test.go @@ -0,0 +1,111 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryProtectionContainer_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test" + vaultName := "azurerm_recovery_services_vault.test" + fabricName := "azurerm_site_recovery_fabric.test" + resourceName := "azurerm_site_recovery_protection_container.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryProtectionContainer_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryProtectionContainerExists(resourceGroupName, vaultName, fabricName, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryProtectionContainer_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric-%d" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_site_recovery_protection_container" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test.name}" + name = "acctest-protection-cont-%d" +} + +`, rInt, location, rInt, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryProtectionContainerExists(resourceGroupStateName, vaultStateName string, resourceStateName string, protectionContainerStateName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + fabricState, ok := s.RootModule().Resources[resourceStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + protectionContainerState, ok := s.RootModule().Resources[protectionContainerStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + fabricName := fabricState.Primary.Attributes["name"] + protectionContainerName := protectionContainerState.Primary.Attributes["name"] + + // Ensure fabric exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ProtectionContainerClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, fabricName, protectionContainerName) + if err != nil { + return fmt.Errorf("Bad: Get on fabricClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: fabric: %q does not exist", fabricName) + } + + return nil + } +} diff --git a/azurerm/resource_arm_site_recovery_replicated_vm.go b/azurerm/resource_arm_site_recovery_replicated_vm.go new file mode 100644 index 000000000000..73a057c04087 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_replicated_vm.go @@ -0,0 +1,352 @@ +package azurerm + +import ( + "bytes" + "fmt" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute" + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryReplicatedVM() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryReplicatedItemCreate, + Read: resourceArmSiteRecoveryReplicatedItemRead, + Delete: resourceArmSiteRecoveryReplicatedItemDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(80 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(80 * time.Minute), + Delete: schema.DefaultTimeout(80 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "source_recovery_fabric_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "source_vm_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_recovery_fabric_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "recovery_replication_policy_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "source_recovery_protection_container_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "target_recovery_protection_container_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_resource_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_availability_set_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "managed_disk": { + Type: schema.TypeSet, + ConfigMode: schema.SchemaConfigModeAttr, + Optional: true, + ForceNew: true, + Set: resourceArmSiteRecoveryReplicatedVMDiskHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disk_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + DiffSuppressFunc: suppress.CaseDifference, + }, + "staging_storage_account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_resource_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_disk_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(compute.StandardLRS), + string(compute.PremiumLRS), + string(compute.StandardSSDLRS), + string(compute.UltraSSDLRS), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + "target_replica_disk_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(compute.StandardLRS), + string(compute.PremiumLRS), + string(compute.StandardSSDLRS), + string(compute.UltraSSDLRS), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + }, + }, + }, + }, + } +} + +func resourceArmSiteRecoveryReplicatedItemCreate(d *schema.ResourceData, meta interface{}) error { + name := d.Get("name").(string) + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + fabricName := d.Get("source_recovery_fabric_name").(string) + sourceVmId := d.Get("source_vm_id").(string) + policyId := d.Get("recovery_replication_policy_id").(string) + sourceProtectionContainerName := d.Get("source_recovery_protection_container_name").(string) + targetProtectionContainerId := d.Get("target_recovery_protection_container_id").(string) + targetResourceGroupId := d.Get("target_resource_group_id").(string) + + var targetAvailabilitySetID *string + if id, isSet := d.GetOk("target_availability_set_id"); isSet { + tmp := id.(string) + targetAvailabilitySetID = &tmp + } else { + targetAvailabilitySetID = nil + } + + client := meta.(*ArmClient).RecoveryServices.ReplicationMigrationItemsClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, fabricName, sourceProtectionContainerName, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery replicated vm %s (vault %s): %+v", name, vaultName, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_replicated_vm", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + managedDisks := []siterecovery.A2AVMManagedDiskInputDetails{} + + for _, raw := range d.Get("managed_disk").(*schema.Set).List() { + diskInput := raw.(map[string]interface{}) + diskId := diskInput["disk_id"].(string) + primaryStagingAzureStorageAccountID := diskInput["staging_storage_account_id"].(string) + recoveryResourceGroupId := diskInput["target_resource_group_id"].(string) + targetReplicaDiskType := diskInput["target_replica_disk_type"].(string) + targetDiskType := diskInput["target_disk_type"].(string) + + managedDisks = append(managedDisks, siterecovery.A2AVMManagedDiskInputDetails{ + DiskID: &diskId, + PrimaryStagingAzureStorageAccountID: &primaryStagingAzureStorageAccountID, + RecoveryResourceGroupID: &recoveryResourceGroupId, + RecoveryReplicaDiskAccountType: &targetReplicaDiskType, + RecoveryTargetDiskAccountType: &targetDiskType, + }) + } + + var parameters = siterecovery.EnableProtectionInput{ + Properties: &siterecovery.EnableProtectionInputProperties{ + PolicyID: &policyId, + ProviderSpecificDetails: siterecovery.A2AEnableProtectionInput{ + FabricObjectID: &sourceVmId, + RecoveryContainerID: &targetProtectionContainerId, + RecoveryResourceGroupID: &targetResourceGroupId, + RecoveryAvailabilitySetID: targetAvailabilitySetID, + VMManagedDisks: &managedDisks, + }, + }, + } + future, err := client.Create(ctx, fabricName, sourceProtectionContainerName, name, parameters) + if err != nil { + return fmt.Errorf("Error creating replicated vm %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating replicated vm %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, fabricName, sourceProtectionContainerName, name) + if err != nil { + return fmt.Errorf("Error retrieving replicated vm %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryReplicatedItemRead(d, meta) +} + +func resourceArmSiteRecoveryReplicatedItemRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + protectionContainerName := id.Path["replicationProtectionContainers"] + name := id.Path["replicationProtectedItems"] + + client := meta.(*ArmClient).RecoveryServices.ReplicationMigrationItemsClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, fabricName, protectionContainerName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on site recovery replicated vm %s (vault %s): %+v", name, vaultName, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resGroup) + d.Set("recovery_vault_name", vaultName) + d.Set("source_recovery_fabric_name", fabricName) + d.Set("target_recovery_fabric_id", resp.Properties.RecoveryFabricID) + d.Set("recovery_replication_policy_id", resp.Properties.PolicyID) + d.Set("source_recovery_protection_container_name", protectionContainerName) + d.Set("target_recovery_protection_container_id", resp.Properties.RecoveryContainerID) + + if a2aDetails, isA2a := resp.Properties.ProviderSpecificDetails.AsA2AReplicationDetails(); isA2a { + d.Set("source_vm_id", a2aDetails.FabricObjectID) + d.Set("target_resource_group_id", a2aDetails.RecoveryAzureResourceGroupID) + d.Set("target_availability_set_id", a2aDetails.RecoveryAvailabilitySet) + if a2aDetails.ProtectedManagedDisks != nil { + disksOutput := make([]interface{}, 0) + for _, disk := range *a2aDetails.ProtectedManagedDisks { + diskOutput := make(map[string]interface{}) + diskOutput["disk_id"] = *disk.DiskID + diskOutput["staging_storage_account_id"] = *disk.PrimaryStagingAzureStorageAccountID + diskOutput["target_resource_group_id"] = *disk.RecoveryResourceGroupID + diskOutput["target_replica_disk_type"] = *disk.RecoveryReplicaDiskAccountType + diskOutput["target_disk_type"] = *disk.RecoveryTargetDiskAccountType + + disksOutput = append(disksOutput, diskOutput) + } + d.Set("managed_disk", schema.NewSet(resourceArmSiteRecoveryReplicatedVMDiskHash, disksOutput)) + } + } + + return nil +} + +func resourceArmSiteRecoveryReplicatedItemDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + fabricName := id.Path["replicationFabrics"] + protectionContainerName := id.Path["replicationProtectionContainers"] + name := id.Path["replicationProtectedItems"] + + disableProtectionInput := siterecovery.DisableProtectionInput{ + Properties: &siterecovery.DisableProtectionInputProperties{ + DisableProtectionReason: siterecovery.NotSpecified, + ReplicationProviderInput: siterecovery.DisableProtectionProviderSpecificInput{}, + }, + } + + client := meta.(*ArmClient).RecoveryServices.ReplicationMigrationItemsClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + future, err := client.Delete(ctx, fabricName, protectionContainerName, name, disableProtectionInput) + if err != nil { + return fmt.Errorf("Error deleting site recovery replicated vm %s (vault %s): %+v", name, vaultName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery replicated vm %s (vault %s): %+v", name, vaultName, err) + } + return nil +} + +func resourceArmSiteRecoveryReplicatedVMDiskHash(v interface{}) int { + var buf bytes.Buffer + + if m, ok := v.(map[string]interface{}); ok { + if v, ok := m["disk_id"]; ok { + buf.WriteString(strings.ToLower(v.(string))) + } + } + + return hashcode.String(buf.String()) +} diff --git a/azurerm/resource_arm_site_recovery_replicated_vm_test.go b/azurerm/resource_arm_site_recovery_replicated_vm_test.go new file mode 100644 index 000000000000..c8f9f7b4e450 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_replicated_vm_test.go @@ -0,0 +1,263 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryReplicatedVm_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test2" + vaultName := "azurerm_recovery_services_vault.test" + fabricName := "azurerm_site_recovery_fabric.test1" + protectionContainerName := "azurerm_site_recovery_protection_container.test1" + replicationName := "azurerm_site_recovery_replicated_vm.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryReplicatedVm_basic(ri, testLocation(), testAltLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryReplicatedVmExists(resourceGroupName, vaultName, fabricName, protectionContainerName, replicationName), + ), + }, + { + ResourceName: replicationName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryReplicatedVm_basic(rInt int, location string, altLocation string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-recovery1-%d" + location = "%s" +} + +resource "azurerm_resource_group" "test2" { + name = "acctestRG-recovery2-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test2.location}" + resource_group_name = "${azurerm_resource_group.test2.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "test1" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric1-%d" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_site_recovery_fabric" "test2" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-fabric2-%d" + location = "${azurerm_resource_group.test2.location}" + depends_on = ["azurerm_site_recovery_fabric.test1"] +} + +resource "azurerm_site_recovery_protection_container" "test1" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + name = "acctest-protection-cont1-%d" +} + +resource "azurerm_site_recovery_protection_container" "test2" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test2.name}" + name = "acctest-protection-cont2-%d" +} + +resource "azurerm_site_recovery_replication_policy" "test" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-policy-%d" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} + +resource "azurerm_site_recovery_protection_container_mapping" "test" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + recovery_source_protection_container_name = "${azurerm_site_recovery_protection_container.test1.name}" + recovery_target_protection_container_id = "${azurerm_site_recovery_protection_container.test2.id}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.test.id}" + name = "mapping-%d" +} + +resource "azurerm_virtual_network" "test1" { + name = "net-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["192.168.1.0/24"] + location = "${azurerm_site_recovery_fabric.test1.location}" +} +resource "azurerm_subnet" "test1" { + name = "snet-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test1.name}" + address_prefix = "192.168.1.0/24" +} + + +resource "azurerm_virtual_network" "test2" { + name = "net-%d" + resource_group_name = "${azurerm_resource_group.test2.name}" + address_space = ["192.168.2.0/24"] + location = "${azurerm_site_recovery_fabric.test2.location}" +} + +resource "azurerm_site_recovery_network_mapping" "test" { + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "mapping-%d" + source_recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + target_recovery_fabric_name = "${azurerm_site_recovery_fabric.test2.name}" + source_network_id = "${azurerm_virtual_network.test1.id}" + target_network_id = "${azurerm_virtual_network.test2.id}" +} + +resource "azurerm_network_interface" "test" { + name = "vm-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "vm-%d" + subnet_id = "${azurerm_subnet.test1.id}" + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_virtual_machine" "test" { + name = "vm-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + vm_size = "Standard_B1s" + + storage_image_reference { + publisher = "OpenLogic" + offer = "CentOS" + sku = "7.5" + version = "latest" + } + + storage_os_disk { + name = "disk-%d" + os_type = "Linux" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Premium_LRS" + } + + os_profile { + admin_username = "testadmin" + admin_password = "Password1234!" + computer_name = "vm-%d" + } + + os_profile_linux_config { + disable_password_authentication = false + + } + network_interface_ids = ["${azurerm_network_interface.test.id}"] +} + +resource "azurerm_storage_account" "test" { + name = "acct%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_site_recovery_replicated_vm" "test" { + name = "repl-%d" + resource_group_name = "${azurerm_resource_group.test2.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + source_vm_id = "${azurerm_virtual_machine.test.id}" + source_recovery_fabric_name = "${azurerm_site_recovery_fabric.test1.name}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.test.id}" + source_recovery_protection_container_name = "${azurerm_site_recovery_protection_container.test1.name}" + + target_resource_group_id = "${azurerm_resource_group.test2.id}" + target_recovery_fabric_id = "${azurerm_site_recovery_fabric.test2.id}" + target_recovery_protection_container_id = "${azurerm_site_recovery_protection_container.test2.id}" + + managed_disk { + disk_id = "${azurerm_virtual_machine.test.storage_os_disk.0.managed_disk_id}" + staging_storage_account_id = "${azurerm_storage_account.test.id}" + target_resource_group_id = "${azurerm_resource_group.test2.id}" + target_disk_type = "Premium_LRS" + target_replica_disk_type = "Premium_LRS" + } + depends_on = ["azurerm_site_recovery_protection_container_mapping.test", "azurerm_site_recovery_network_mapping.test"] +} +`, rInt, location, rInt, altLocation, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryReplicatedVmExists(resourceGroupStateName, vaultStateName string, resourceStateName string, protectionContainerStateName string, replicationStateName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + fabricState, ok := s.RootModule().Resources[resourceStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + protectionContainerState, ok := s.RootModule().Resources[protectionContainerStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceStateName) + } + replicationState, ok := s.RootModule().Resources[replicationStateName] + if !ok { + return fmt.Errorf("Not found: %s", replicationStateName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + fabricName := fabricState.Primary.Attributes["name"] + protectionContainerName := protectionContainerState.Primary.Attributes["name"] + replicationName := replicationState.Primary.Attributes["name"] + + // Ensure mapping exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ReplicationMigrationItemsClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, fabricName, protectionContainerName, replicationName) + if err != nil { + return fmt.Errorf("Bad: Get on replicationVmClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: fabric: %q does not exist", fabricName) + } + + return nil + } +} diff --git a/azurerm/resource_arm_site_recovery_replication_policy.go b/azurerm/resource_arm_site_recovery_replication_policy.go new file mode 100644 index 000000000000..779ac3d37cd4 --- /dev/null +++ b/azurerm/resource_arm_site_recovery_replication_policy.go @@ -0,0 +1,214 @@ +package azurerm + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2018-01-10/siterecovery" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSiteRecoveryReplicationPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSiteRecoveryReplicationPolicyCreate, + Read: resourceArmSiteRecoveryReplicationPolicyRead, + Update: resourceArmSiteRecoveryReplicationPolicyUpdate, + Delete: resourceArmSiteRecoveryReplicationPolicyDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "resource_group_name": azure.SchemaResourceGroupName(), + + "recovery_vault_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateRecoveryServicesVaultName, + }, + "recovery_point_retention_in_minutes": { + Type: schema.TypeInt, + Required: true, + ForceNew: false, + ValidateFunc: validation.IntBetween(1, 365*24*60), + }, + "application_consistent_snapshot_frequency_in_minutes": { + Type: schema.TypeInt, + Required: true, + ForceNew: false, + ValidateFunc: validation.IntBetween(1, 365*24*60), + }, + }, + } +} + +func resourceArmSiteRecoveryReplicationPolicyCreate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.ReplicationPoliciesClient(resGroup, vaultName) + ctx, cancel := timeouts.ForCreate(meta.(*ArmClient).StopContext, d) + defer cancel() + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for presence of existing site recovery replication policy %s: %+v", name, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_site_recovery_replication_policy", azure.HandleAzureSdkForGoBug2824(*existing.ID)) + } + } + + recoveryPoint := int32(d.Get("recovery_point_retention_in_minutes").(int)) + appConsitency := int32(d.Get("application_consistent_snapshot_frequency_in_minutes").(int)) + var parameters = siterecovery.CreatePolicyInput{ + Properties: &siterecovery.CreatePolicyInputProperties{ + ProviderSpecificInput: &siterecovery.A2APolicyCreationInput{ + RecoveryPointHistory: &recoveryPoint, + AppConsistentFrequencyInMinutes: &appConsitency, + MultiVMSyncStatus: siterecovery.Enable, + InstanceType: siterecovery.InstanceTypeBasicPolicyProviderSpecificInputInstanceTypeA2A, + }, + }, + } + future, err := client.Create(ctx, name, parameters) + if err != nil { + return fmt.Errorf("Error creating site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error creating site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryReplicationPolicyRead(d, meta) +} + +func resourceArmSiteRecoveryReplicationPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + resGroup := d.Get("resource_group_name").(string) + vaultName := d.Get("recovery_vault_name").(string) + name := d.Get("name").(string) + + client := meta.(*ArmClient).RecoveryServices.ReplicationPoliciesClient(resGroup, vaultName) + ctx, cancel := timeouts.ForUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + recoveryPoint := int32(d.Get("recovery_point_retention_in_minutes").(int)) + appConsitency := int32(d.Get("application_consistent_snapshot_frequency_in_minutes").(int)) + var parameters = siterecovery.UpdatePolicyInput{ + Properties: &siterecovery.UpdatePolicyInputProperties{ + ReplicationProviderSettings: &siterecovery.A2APolicyCreationInput{ + RecoveryPointHistory: &recoveryPoint, + AppConsistentFrequencyInMinutes: &appConsitency, + MultiVMSyncStatus: siterecovery.Enable, + InstanceType: siterecovery.InstanceTypeBasicPolicyProviderSpecificInputInstanceTypeA2A, + }, + }, + } + future, err := client.Update(ctx, name, parameters) + if err != nil { + return fmt.Errorf("Error updating site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error updating site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + resp, err := client.Get(ctx, name) + if err != nil { + return fmt.Errorf("Error retrieving site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID)) + + return resourceArmSiteRecoveryReplicationPolicyRead(d, meta) +} + +func resourceArmSiteRecoveryReplicationPolicyRead(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + name := id.Path["replicationPolicies"] + + client := meta.(*ArmClient).RecoveryServices.ReplicationPoliciesClient(resGroup, vaultName) + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + resp, err := client.Get(ctx, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("recovery_vault_name", vaultName) + if a2APolicyDetails, isA2A := resp.Properties.ProviderSpecificDetails.AsA2APolicyDetails(); isA2A { + d.Set("recovery_point_retention_in_minutes", a2APolicyDetails.RecoveryPointHistory) + d.Set("application_consistent_snapshot_frequency_in_minutes", a2APolicyDetails.AppConsistentFrequencyInMinutes) + } + return nil +} + +func resourceArmSiteRecoveryReplicationPolicyDelete(d *schema.ResourceData, meta interface{}) error { + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resGroup := id.ResourceGroup + vaultName := id.Path["vaults"] + name := id.Path["replicationPolicies"] + + client := meta.(*ArmClient).RecoveryServices.ReplicationPoliciesClient(resGroup, vaultName) + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + future, err := client.Delete(ctx, name) + if err != nil { + return fmt.Errorf("Error deleting site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of site recovery replication policy %s (vault %s): %+v", name, vaultName, err) + } + + return nil +} diff --git a/azurerm/resource_arm_site_recovery_replication_policy_test.go b/azurerm/resource_arm_site_recovery_replication_policy_test.go new file mode 100644 index 000000000000..dc40101ad39d --- /dev/null +++ b/azurerm/resource_arm_site_recovery_replication_policy_test.go @@ -0,0 +1,98 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMSiteRecoveryReplicationPolicy_basic(t *testing.T) { + resourceGroupName := "azurerm_resource_group.test" + vaultName := "azurerm_recovery_services_vault.test" + resourceName := "azurerm_site_recovery_replication_policy.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMResourceGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSiteRecoveryReplicationPolicy_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSiteRecoveryReplicationPolicyExists(resourceGroupName, vaultName, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMSiteRecoveryReplicationPolicy_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_replication_policy" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.test.name}" + name = "acctest-policy-%d" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} +`, rInt, location, rInt, rInt) +} + +func testCheckAzureRMSiteRecoveryReplicationPolicyExists(resourceGroupStateName, vaultStateName string, policyStateName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + resourceGroupState, ok := s.RootModule().Resources[resourceGroupStateName] + if !ok { + return fmt.Errorf("Not found: %s", resourceGroupStateName) + } + vaultState, ok := s.RootModule().Resources[vaultStateName] + if !ok { + return fmt.Errorf("Not found: %s", vaultStateName) + } + policyState, ok := s.RootModule().Resources[policyStateName] + if !ok { + return fmt.Errorf("Not found: %s", policyStateName) + } + + resourceGroupName := resourceGroupState.Primary.Attributes["name"] + vaultName := vaultState.Primary.Attributes["name"] + policyName := policyState.Primary.Attributes["name"] + + // Ensure fabric exists in API + client := testAccProvider.Meta().(*ArmClient).RecoveryServices.ReplicationPoliciesClient(resourceGroupName, vaultName) + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + resp, err := client.Get(ctx, policyName) + if err != nil { + return fmt.Errorf("Bad: Get on fabricClient: %+v", err) + } + + if resp.Response.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: fabric: %q does not exist", policyName) + } + + return nil + } +} diff --git a/website/docs/r/backup_policy_vm.markdown b/website/docs/r/backup_policy_vm.markdown new file mode 100644 index 000000000000..77b7f7474242 --- /dev/null +++ b/website/docs/r/backup_policy_vm.markdown @@ -0,0 +1,151 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_backup_policy_vm" +sidebar_current: "docs-azurerm-backup-policy-vm" +description: |- + Manages an Azure Backup VM Backup Policy. +--- + +# azurerm_backup_policy_vm + +Manages an Azure Backup VM Backup Policy. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "tfex-recovery_vault" + location = "West US" +} + +resource "azurerm_recovery_services_vault" "example" { + name = "tfex-recovery-vault" + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + sku = "Standard" +} + +resource "azurerm_backup_policy_vm" "example" { + name = "tfex-recovery-vault-policy" + resource_group_name = "${azurerm_resource_group.example.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.example.name}" + + timezone = "UTC" + + backup { + frequency = "Daily" + time = "23:00" + } + + retention_daily { + count = 10 + } + + retention_weekly { + count = 42 + weekdays = ["Sunday", "Wednesday", "Friday", "Saturday"] + } + + retention_monthly { + count = 7 + weekdays = ["Sunday", "Wednesday"] + weeks = ["First", "Last"] + } + + retention_yearly { + count = 77 + weekdays = ["Sunday"] + weeks = ["Last"] + months = ["January"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the Backup Policy. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the policy. Changing this forces a new resource to be created. + +* `recovery_vault_name` - (Required) Specifies the name of the Recovery Services Vault to use. Changing this forces a new resource to be created. + +* `backup` - (Required) Configures the Policy backup frequecent, times & days as documented in the `backup` block below. + +* `timezone` - (Optional) Specifies the timezone. Defaults to `UTC` + +* `retention_daily` - (Optional) Configures the policy daily retention as documented in the `retention_daily` block below. Required when backup frequency is `Daily`. + +* `retention_weekly` - (Optional) Configures the policy weekly retention as documented in the `retention_weekly` block below. Required when backup frequency is `Weekly`. + +* `retention_monthly` - (Optional) Configures the policy monthly retention as documented in the `retention_monthly` block below. + +* `retention_yearly` - (Optional) Configures the policy yearly retention as documented in the `retention_yearly` block below. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +--- + +The `backup` block supports: + +* `frequency` - (Required) Sets the backup frequency. Must be either `Daily` or`Weekly`. + +* `times` - (Required) The time of day to perform the backup in 24hour format. + +* `weekdays` - (Optional) The days of the week to perform backups on. Must be one of `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday` or `Saturday`. + +--- + +The `retention_daily` block supports: + +* `count` - (Required) The number of daily backups to keep. Must be between `1` and `9999` + +--- + +The `retention_weekly` block supports: + +* `count` - (Required) The number of weekly backups to keep. Must be between `1` and `9999` + +* `weekdays` - (Required) The weekday backups to retain. Must be one of `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday` or `Saturday`. + +--- + +The `retention_monthly` block supports: + +* `count` - (Required) The number of monthly backups to keep. Must be between `1` and `9999` + +* `weekdays` - (Required) The weekday backups to retain . Must be one of `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday` or `Saturday`. + +* `weeks` - (Required) The weeks of the month to retain backups of. Must be one of `First`, `Second`, `Third`, `Fourth`, `Last`. + +--- + +The `retention_yearly` block supports: + +* `count` - (Required) The number of yearly backups to keep. Must be between `1` and `9999` + +* `weekdays` - (Required) The weekday backups to retain . Must be one of `Sunday`, `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday` or `Saturday`. + +* `weeks` - (Required) The weeks of the month to retain backups of. Must be one of `First`, `Second`, `Third`, `Fourth`, `Last`. + +* `months` - (Required) The months of the year to retain backups of. Must be one of `January`, `February`, `March`, `April`, `May`, `June`, `July`, `Augest`, `September`, `October`, `November` and `December`. + +--- + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the VM Backup Policy. + +## Import + +VM Backup Policies can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_backup_policy_vm.policy1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.RecoveryServices/vaults/example-recovery-vault/backupPolicies/policy1 +``` + + diff --git a/website/docs/r/backup_protected_vm.markdown b/website/docs/r/backup_protected_vm.markdown new file mode 100644 index 000000000000..25648173af5a --- /dev/null +++ b/website/docs/r/backup_protected_vm.markdown @@ -0,0 +1,77 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_backup_protected_vm" +sidebar_current: "docs-azurerm-backup-protected-vm" +description: |- + Manages an Azure Backup Protected VM. +--- + +# azurerm_backup_protected_vm + +Manages Azure Backup for an Azure VM + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "tfex-recovery_vault" + location = "West US" +} + +resource "azurerm_recovery_services_vault" "example" { + name = "tfex-recovery-vault" + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" + sku = "Standard" +} + +resource "azurerm_backup_policy_vm" "example" { + name = "tfex-recovery-vault-policy" + resource_group_name = "${azurerm_resource_group.example.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.example.name}" + + backup { + frequency = "Daily" + time = "23:00" + } +} + +resource "azurerm_backup_protected_vm" "vm1" { + resource_group_name = "${azurerm_resource_group.example.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.example.name}" + source_vm_id = "${azurerm_virtual_machine.example.id}" + backup_policy_id = "${azurerm_backup_policy_vm.example.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `resource_group_name` - (Required) The name of the resource group in which to create the Recovery Services Protected VM. Changing this forces a new resource to be created. + +* `recovery_vault_name` - (Required) Specifies the name of the Recovery Services Vault to use. Changing this forces a new resource to be created. + +* `source_vm_id` - (Required) Specifies the ID of the VM to backup. Changing this forces a new resource to be created. + +* `backup_policy_id` - (Required) Specifies the id of the backup policy to use. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the Recovery Services Vault. + +## Import + +Recovery Services Protected VMs can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_backup_protected_vm.item1 "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.RecoveryServices/vaults/example-recovery-vault/backupFabrics/Azure/protectionContainers/iaasvmcontainer;iaasvmcontainerv2;group1;vm1/protectedItems/vm;iaasvmcontainerv2;group1;vm1" +``` + +Note the ID requires quoting as there are semicolons + \ No newline at end of file diff --git a/website/docs/r/recovery_network_mapping.html.markdown b/website/docs/r/recovery_network_mapping.html.markdown index 28e9e5e8f15c..7dcfc07874cc 100644 --- a/website/docs/r/recovery_network_mapping.html.markdown +++ b/website/docs/r/recovery_network_mapping.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_network_mapping +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_network_mapping` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a site recovery network mapping on Azure. A network mapping decides how to translate connected netwroks when a VM is migrated from one region to another. ## Example Usage diff --git a/website/docs/r/recovery_services_fabric.html.markdown b/website/docs/r/recovery_services_fabric.html.markdown index a32b46410418..99fcb4550a39 100644 --- a/website/docs/r/recovery_services_fabric.html.markdown +++ b/website/docs/r/recovery_services_fabric.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_fabric +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_fabric` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a Azure recovery vault fabric. ## Example Usage diff --git a/website/docs/r/recovery_services_protected_vm.markdown b/website/docs/r/recovery_services_protected_vm.markdown index d709c9aee427..888dfbaa4453 100644 --- a/website/docs/r/recovery_services_protected_vm.markdown +++ b/website/docs/r/recovery_services_protected_vm.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_protected_vm +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_backup_protected_vm` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages an Recovery Protected VM. ## Example Usage diff --git a/website/docs/r/recovery_services_protection_container.html.markdown b/website/docs/r/recovery_services_protection_container.html.markdown index b29dadebb860..8bb9e733dea7 100644 --- a/website/docs/r/recovery_services_protection_container.html.markdown +++ b/website/docs/r/recovery_services_protection_container.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_protection_container +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_protection_container` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a Azure recovery vault protection container. ## Example Usage diff --git a/website/docs/r/recovery_services_protection_container_mapping.html.markdown b/website/docs/r/recovery_services_protection_container_mapping.html.markdown index fa7b18fd6f8e..fcc2defd911c 100644 --- a/website/docs/r/recovery_services_protection_container_mapping.html.markdown +++ b/website/docs/r/recovery_services_protection_container_mapping.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_protection_container_mapping +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_protection_container_mapping` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a Azure recovery vault protection container mapping. A network protection container mapping decides how to translate the protection container when a VM is migrated from one region to another. ## Example Usage diff --git a/website/docs/r/recovery_services_protection_policy_vm.markdown b/website/docs/r/recovery_services_protection_policy_vm.markdown index 895ba95c55bb..e43fa67b4a2d 100644 --- a/website/docs/r/recovery_services_protection_policy_vm.markdown +++ b/website/docs/r/recovery_services_protection_policy_vm.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_protection_policy_vm +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_backup_policy_vm` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages an Recovery Services VM Protection Policy. ## Example Usage diff --git a/website/docs/r/recovery_services_replicated_vm.html.markdown b/website/docs/r/recovery_services_replicated_vm.html.markdown index d2ff4091f949..54cee3ceb854 100644 --- a/website/docs/r/recovery_services_replicated_vm.html.markdown +++ b/website/docs/r/recovery_services_replicated_vm.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_replicated_vm +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_replicated_vm` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a Azure recovery replicated vms (Azure to Azure). An replicated VM keeps a copiously updated image of the vm in another region in order to be able to start the VM in that region in case of a disaster. ## Example Usage @@ -219,3 +221,4 @@ Site recovery recovery vault fabric can be imported using the `resource id`, e.g ```shell terraform import azurerm_recovery_replicated_vm.vmreplication /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationFabrics/fabric-name/replicationProtectionContainers/protection-container-name/replicationProtectedItems/vm-replication-name +``` diff --git a/website/docs/r/recovery_services_replication_policy.html.markdown b/website/docs/r/recovery_services_replication_policy.html.markdown index 425b8e6b125e..c4936acf7cbb 100644 --- a/website/docs/r/recovery_services_replication_policy.html.markdown +++ b/website/docs/r/recovery_services_replication_policy.html.markdown @@ -9,6 +9,8 @@ description: |- # azurerm_recovery_services_replication_policy +~> **NOTE:** This resource has been deprecated in favour of the `azurerm_site_recovery_replication_policy` resource and will be removed in the next major version of the AzureRM Provider. The new resource shares the same fields as this one, and information on migrating across [can be found in this guide](../guides/migrating-between-renamed-resources.html). + Manages a Azure recovery vault replication policy. ## Example Usage diff --git a/website/docs/r/site_recovery_fabric.html.markdown b/website/docs/r/site_recovery_fabric.html.markdown new file mode 100644 index 000000000000..828fa95cdf5b --- /dev/null +++ b/website/docs/r/site_recovery_fabric.html.markdown @@ -0,0 +1,66 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_fabric" +sidebar_current: "docs-azurerm-site-recovery-replication-fabric" +description: |- + Manages a Site Recovery Replication Fabric on Azure. +--- + +# azurerm_site_recovery_fabric + +Manages a Azure Site Recovery Replication Fabric within a Recovery Services vault. Only Azure fabrics are supported at this time. Replication Fabrics serve as a container within an Azure region for other Site Recovery resources such as protection containers, protected items, network mappings. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "primary" { + name = "tfex-network-mapping-primary" + location = "West US" +} + +resource "azurerm_resource_group" "secondary" { + name = "tfex-network-mapping-secondary" + location = "East US" +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "fabric" { + name = "primary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.primary.location}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `location` - (Required) In what region should the fabric be located. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site recovery recovery vault fabric can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_fabric.myfabric /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationFabrics/fabric-name +``` diff --git a/website/docs/r/site_recovery_network_mapping.html.markdown b/website/docs/r/site_recovery_network_mapping.html.markdown new file mode 100644 index 000000000000..7568384f7e9b --- /dev/null +++ b/website/docs/r/site_recovery_network_mapping.html.markdown @@ -0,0 +1,104 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_network_mapping" +sidebar_current: "docs-azurerm-site-recovery-network-mapping" +description: |- + Manages a site recovery network mapping on Azure. +--- + +# azurerm_site_recovery_network_mapping + +Manages a site recovery network mapping on Azure. A network mapping decides how to translate connected netwroks when a VM is migrated from one region to another. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "primary" { + name = "tfex-network-mapping-primary" + location = "West US" +} + +resource "azurerm_resource_group" "secondary" { + name = "tfex-network-mapping-secondary" + location = "East US" +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "primary" { + name = "primary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_site_recovery_fabric" "secondary" { + name = "secondary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.secondary.location}" + depends_on = ["azurerm_site_recovery_fabric.primary"] # Avoids issues with crearing fabrics simultainusly +} + +resource "azurerm_virtual_network" "primary" { + name = "network1" + resource_group_name = "${azurerm_resource_group.primary.name}" + address_space = ["192.168.1.0/24"] + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_virtual_network" "secondary" { + name = "network2" + resource_group_name = "${azurerm_resource_group.secondary.name}" + address_space = ["192.168.2.0/24"] + location = "${azurerm_resource_group.secondary.location}" +} + +resource "azurerm_site_recovery_network_mapping" "recovery-mapping" { + name = "recovery-network-mapping-1" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + source_recovery_fabric_name = "primary-fabric" + target_recovery_fabric_name = "secondary-fabric" + source_network_id = "${azurerm_virtual_network.primary.id}" + target_network_id = "${azurerm_virtual_network.secondary.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `source_recovery_fabric_name` - (Required) Specifies the ASR fabric where mapping should be created. + +* `target_recovery_fabric_name` - (Required) The Azure Site Recovery fabric object corresponding to the recovery Azure region. + +* `source_network_id` - (Required) The id of the primary network. + +* `target_network_id` - (Required) The id of the recovery network. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site recovery network mapping can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_network_mapping.mymapping /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationFabrics/primary-fabric-name/replicationNetworks/azureNetwork/replicationNetworkMappings/mapping-name +``` diff --git a/website/docs/r/site_recovery_protection_container.html.markdown b/website/docs/r/site_recovery_protection_container.html.markdown new file mode 100644 index 000000000000..fb48f350eba2 --- /dev/null +++ b/website/docs/r/site_recovery_protection_container.html.markdown @@ -0,0 +1,73 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_protection_container" +sidebar_current: "docs-azurerm-site-recovery-protection-container" +description: |- + Manages a site recovery services protection container on Azure. +--- + +# azurerm_site_recovery_protection_container + +Manages a Azure Site Recovery protection container. Protection containers serve as containers for replicated VMs and belong to a single region / recovery fabric. Protection containers can contain more than one replicated VM. To replicate a VM, a container must exist in both the source and target Azure regions. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "primary" { + name = "tfex-network-mapping-primary" + location = "West US" +} + +resource "azurerm_resource_group" "secondary" { + name = "tfex-network-mapping-secondary" + location = "East US" +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "fabric" { + name = "primary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_site_recovery_protection_container" "protection-container" { + name = "protection-container" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.fabric.name}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `recovery_fabric_name` - (Required) Name of fabric that should contain this protection container. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site Recovery protection container can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_protection_container.mycontainer /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationFabrics/fabric-name/replicationProtectionContainers/protection-container-name +``` diff --git a/website/docs/r/site_recovery_protection_container_mapping.html.markdown b/website/docs/r/site_recovery_protection_container_mapping.html.markdown new file mode 100644 index 000000000000..c02f194ddbd2 --- /dev/null +++ b/website/docs/r/site_recovery_protection_container_mapping.html.markdown @@ -0,0 +1,111 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_protection_container_mapping" +sidebar_current: "docs-azurerm-recovery-services-protection-container-mapping" +description: |- + Manages a Site Recovery protection container mapping on Azure. +--- + +# azurerm_site_recovery_protection_container_mapping + +Manages a Azure recovery vault protection container mapping. A protection container mapping decides how to translate the protection container when a VM is migrated from one region to another. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "primary" { + name = "tfex-network-mapping-primary" + location = "West US" +} + +resource "azurerm_resource_group" "secondary" { + name = "tfex-network-mapping-secondary" + location = "East US" +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "primary" { + name = "primary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_site_recovery_fabric" "secondary" { + name = "secondary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.secondary.location}" +} + +resource "azurerm_site_recovery_protection_container" "primary" { + name = "primary-protection-container" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.primary.name}" +} + +resource "azurerm_site_recovery_protection_container" "secondary" { + name = "secondary-protection-container" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.secondary.name}" +} + +resource "azurerm_site_recovery_replication_policy" "policy" { + name = "policy" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} + +resource "azurerm_site_recovery_protection_container_mapping" "container-mapping" { + name = "container-mapping" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.primary.name}" + recovery_source_protection_container_name = "${azurerm_site_recovery_protection_container.primary.name}" + recovery_target_protection_container_id = "${azurerm_site_recovery_protection_container.secondary.id}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.policy.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `recovery_fabric_name` - (Required) Name of fabric that should contains the protection container to map. + +* `recovery_source_protection_container_name` - (Required) Name of the source protection container to map. + +* `recovery_target_protection_container_id` - (Required) Id of target protection container to map to. + +* `recovery_replication_policy_id` - (Required) Id of the policy to use for this mapping. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site Recovery protection container mapping can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_protection_container_mapping.mymapping /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name +``` diff --git a/website/docs/r/site_recovery_replicated_vm.html.markdown b/website/docs/r/site_recovery_replicated_vm.html.markdown new file mode 100644 index 000000000000..47e759b59809 --- /dev/null +++ b/website/docs/r/site_recovery_replicated_vm.html.markdown @@ -0,0 +1,222 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_replicated_vm" +sidebar_current: "docs-azurerm-site-recovery-replicated-vm" +description: |- + Manages a VM protected with Azure Site Recovery on Azure. +--- + +# azurerm_site_recovery_replicated_vm + +Manages a VM replicated using Azure Site Recovery (Azure to Azure only). A replicated VM keeps a copiously updated image of the VM in another region in order to be able to start the VM in that region in case of a disaster. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "primary" { + name = "tfex-replicated-vm-primary" + location = "West US" +} + +resource "azurerm_resource_group" "secondary" { + name = "tfex-replicated-vm-secondary" + location = "East US" +} + +resource "azurerm_virtual_machine" "vm" { + name = "vm" + location = "${azurerm_resource_group.primary.location}" + resource_group_name = "${azurerm_resource_group.primary.name}" + vm_size = "Standard_B1s" + network_interface_ids = ["${azurerm_network_interface.vm.id}"] + + storage_image_reference { + publisher = "OpenLogic" + offer = "CentOS" + sku = "7.5" + version = "latest" + } + + storage_os_disk { + name = "vm-os-disk" + os_type = "Linux" + caching = "ReadWrite" + create_option = "FromImage" + managed_disk_type = "Premium_LRS" + } + + os_profile { + admin_username = "test-admin-123" + admin_password = "test-pwd-123" + computer_name = "vm" + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_fabric" "primary" { + name = "primary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_site_recovery_fabric" "secondary" { + name = "secondary-fabric" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + location = "${azurerm_resource_group.secondary.location}" +} + +resource "azurerm_site_recovery_protection_container" "primary" { + name = "primary-protection-container" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.primary.name}" +} + +resource "azurerm_site_recovery_protection_container" "secondary" { + name = "secondary-protection-container" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.secondary.name}" +} + +resource "azurerm_site_recovery_replication_policy" "policy" { + name = "policy" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} + +resource "azurerm_site_recovery_protection_container_mapping" "container-mapping" { + name = "container-mapping" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_fabric_name = "${azurerm_site_recovery_fabric.primary.name}" + recovery_source_protection_container_name = "${azurerm_site_recovery_protection_container.primary.name}" + recovery_target_protection_container_id = "${azurerm_site_recovery_protection_container.secondary.id}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.policy.id}" +} + +resource "azurerm_storage_account" "primary" { + name = "primaryrecoverycache" + location = "${azurerm_resource_group.primary.location}" + resource_group_name = "${azurerm_resource_group.primary.name}" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_virtual_network" "primary" { + name = "network1" + resource_group_name = "${azurerm_resource_group.primary.name}" + address_space = ["192.168.1.0/24"] + location = "${azurerm_resource_group.primary.location}" +} + +resource "azurerm_subnet" "primary" { + name = "network1-subnet" + resource_group_name = "${azurerm_resource_group.primary.name}" + virtual_network_name = "${azurerm_virtual_network.primary.name}" + address_prefix = "192.168.1.0/24" +} + +resource "azurerm_network_interface" "vm" { + name = "vm-nic" + location = "${azurerm_resource_group.primary.location}" + resource_group_name = "${azurerm_resource_group.primary.name}" + + ip_configuration { + name = "vm" + subnet_id = "${azurerm_subnet.primary.id}" + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_site_recovery_replicated_vm" "vm-replication" { + name = "vm-replication" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + source_recovery_fabric_name = "${azurerm_site_recovery_fabric.primary.name}" + source_vm_id = "${azurerm_virtual_machine.vm.id}" + recovery_replication_policy_id = "${azurerm_site_recovery_replication_policy.policy.id}" + source_recovery_protection_container_name = "${azurerm_site_recovery_protection_container.primary.name}" + + target_resource_group_id = "${azurerm_resource_group.secondary.id}" + target_recovery_fabric_id = "${azurerm_site_recovery_fabric.secondary.id}" + target_recovery_protection_container_id = "${azurerm_site_recovery_protection_container.secondary.id}" + + managed_disk { + disk_id = "${azurerm_virtual_machine.vm.storage_os_disk.0.managed_disk_id}" + staging_storage_account_id = "${azurerm_storage_account.primary.id}" + target_resource_group_id = "${azurerm_resource_group.secondary.id}" + target_disk_type = "Premium_LRS" + target_replica_disk_type = "Premium_LRS" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `source_recovery_fabric_name` - (Required) Name of fabric that should contains this replication. + +* `source_vm_id` - (Required) Id of the VM to replicate + +* `source_recovery_protection_container_name` - (Required) Name of the protection container to use. + +* `target_resource_group_id` - (Required) Id of resource group where the VM should be created when a failover is done. + +* `target_recovery_fabric_id` - (Required) Id of fabric where the VM replication should be handled when a failover is done. + +* `target_recovery_protection_container_id` - (Required) Id of protection container where the VM replication should be created when a failover is done. + +* `target_availability_set_id` - (Optional) Id of availability set that the new VM should belong to when a failover is done. + +* `managed_disk` - (Required) One or more `managed_disk` block. + +--- + +A `managed_disk` block supports the following: + +* `disk_id` - (Required) Id of disk that should be replicated. + +* `staging_storage_account_id` - (Required) Storage account that should be used for caching. + +* `target_resource_group_id` - (Required) Resource group disk should belong to when a failover is done. + +* `target_disk_type` - (Required) What type should the disk be when a failover is done. + +* `target_replica_disk_type` - (Required) What type should the disk be that holds the replication data. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site Recovery replicated VMs can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_replicated_vm.vmreplication /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationFabrics/fabric-name/replicationProtectionContainers/protection-container-name/replicationProtectedItems/vm-replication-name +``` diff --git a/website/docs/r/site_recovery_replication_policy.html.markdown b/website/docs/r/site_recovery_replication_policy.html.markdown new file mode 100644 index 000000000000..fe828280fe17 --- /dev/null +++ b/website/docs/r/site_recovery_replication_policy.html.markdown @@ -0,0 +1,64 @@ +--- +subcategory: "Recovery Services" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_site_recovery_replication_policy" +sidebar_current: "docs-azurerm-site-recovery-replication-policy" +description: |- + Manages an Azure Site Recovery replication policy on Azure. +--- + +# azurerm_site_recovery_replication_policy + +Manages a Azure Site Recovery replication policy within a recovery vault. Replication policies define the frequency at which recovery points are created and how long they are stored. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "secondary" { + name = "tfex-network-mapping-secondary" + location = "East US" +} + +resource "azurerm_recovery_services_vault" "vault" { + name = "example-recovery-vault" + location = "${azurerm_resource_group.secondary.location}" + resource_group_name = "${azurerm_resource_group.secondary.name}" + sku = "Standard" +} + +resource "azurerm_site_recovery_replication_policy" "policy" { + name = "policy" + resource_group_name = "${azurerm_resource_group.secondary.name}" + recovery_vault_name = "${azurerm_recovery_services_vault.vault.name}" + recovery_point_retention_in_minutes = "${24 * 60}" + application_consistent_snapshot_frequency_in_minutes = "${4 * 60}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network mapping. + +* `resource_group_name` - (Required) Name of the resource group where the vault that should be updated is located. + +* `recovery_vault_name` - (Required) The name of the vault that should be updated. + +* `recovery_point_retention_in_minutes` - (Required) The duration in minutes for which the recovery points need to be stored. + +* `application_consistent_snapshot_frequency_in_minutes` - (Required) Specifies the frequency(in minutes) at which to create application consistent recovery points. + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `id` - The resource ID. + +## Import + +Site Recovery replication policies can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_site_recovery_replication_policy.mypolicy /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resource-group-name/providers/Microsoft.RecoveryServices/vaults/recovery-vault-name/replicationPolicies/policy-name +```