From 7fab56402ec69350b58acc9190d1cd32e93b7c3c Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 9 Jul 2024 18:54:52 -0700 Subject: [PATCH 1/2] azurerm_backup_protected_vm - split create and update function to fix lifecycle - ignore --- .../backup_protected_vm_resource.go | 191 +++++++++++++----- 1 file changed, 139 insertions(+), 52 deletions(-) diff --git a/internal/services/recoveryservices/backup_protected_vm_resource.go b/internal/services/recoveryservices/backup_protected_vm_resource.go index 932f6a86f8cb..85f4bfe13ffa 100644 --- a/internal/services/recoveryservices/backup_protected_vm_resource.go +++ b/internal/services/recoveryservices/backup_protected_vm_resource.go @@ -32,9 +32,9 @@ import ( func resourceRecoveryServicesBackupProtectedVM() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceRecoveryServicesBackupProtectedVMCreateUpdate, + Create: resourceRecoveryServicesBackupProtectedVMCreate, Read: resourceRecoveryServicesBackupProtectedVMRead, - Update: resourceRecoveryServicesBackupProtectedVMCreateUpdate, + Update: resourceRecoveryServicesBackupProtectedVMUpdate, Delete: resourceRecoveryServicesBackupProtectedVMDelete, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { @@ -59,11 +59,11 @@ func resourceRecoveryServicesBackupProtectedVM() *pluginsdk.Resource { } } -func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { +func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).RecoveryServices.ProtectedItemsClient opClient := meta.(*clients.Client).RecoveryServices.ProtectedItemOperationResultsClient subscriptionId := meta.(*clients.Client).Account.SubscriptionId - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() resourceGroup := d.Get("resource_group_name").(string) @@ -71,10 +71,8 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource vaultName := d.Get("recovery_vault_name").(string) // source_vm_id must be specified at creation time but can be removed during update - if d.IsNewResource() { - if _, ok := d.GetOk("source_vm_id"); !ok { - return fmt.Errorf("`source_vm_id` must be specified when creating") - } + if _, ok := d.GetOk("source_vm_id"); !ok { + return fmt.Errorf("`source_vm_id` must be specified when creating") } vmId := d.Get("source_vm_id").(string) @@ -97,38 +95,35 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource id := protecteditems.NewProtectedItemID(subscriptionId, resourceGroup, vaultName, "Azure", containerName, protectedItemName) - if d.IsNewResource() { - existing, err := client.Get(ctx, id, protecteditems.GetOperationOptions{}) - if err != nil { - if !response.WasNotFound(existing.HttpResponse) { - return fmt.Errorf("checking for presence of existing %s: %+v", id, err) - } + existing, err := client.Get(ctx, id, protecteditems.GetOperationOptions{}) + if err != nil { + if !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) } + } - if !response.WasNotFound(existing.HttpResponse) { - isSoftDeleted := false - if existing.Model != nil && existing.Model.Properties != nil { - if prop, ok := existing.Model.Properties.(protecteditems.AzureIaaSComputeVMProtectedItem); ok { - isSoftDeleted = pointer.From(prop.IsScheduledForDeferredDelete) - } + if !response.WasNotFound(existing.HttpResponse) { + isSoftDeleted := false + if existing.Model != nil && existing.Model.Properties != nil { + if prop, ok := existing.Model.Properties.(protecteditems.AzureIaaSComputeVMProtectedItem); ok { + isSoftDeleted = pointer.From(prop.IsScheduledForDeferredDelete) } + } - if isSoftDeleted { - if meta.(*clients.Client).Features.RecoveryServicesVault.RecoverSoftDeletedBackupProtectedVM { - err = resourceRecoveryServicesVaultBackupProtectedVMRecoverSoftDeleted(ctx, client, opClient, id) - if err != nil { - return fmt.Errorf("recovering soft deleted %s: %+v", id, err) - } - } else { - return fmt.Errorf(optedOutOfRecoveringSoftDeletedBackupProtectedVMFmt(parsedVmId.ID(), vaultName)) + if isSoftDeleted { + if meta.(*clients.Client).Features.RecoveryServicesVault.RecoverSoftDeletedBackupProtectedVM { + err = resourceRecoveryServicesVaultBackupProtectedVMRecoverSoftDeleted(ctx, client, opClient, id) + if err != nil { + return fmt.Errorf("recovering soft deleted %s: %+v", id, err) } - } - - if !isSoftDeleted { - return tf.ImportAsExistsError("azurerm_backup_protected_vm", id.ID()) + } else { + return fmt.Errorf(optedOutOfRecoveringSoftDeletedBackupProtectedVMFmt(parsedVmId.ID(), vaultName)) } } + if !isSoftDeleted { + return tf.ImportAsExistsError("azurerm_backup_protected_vm", id.ID()) + } } item := protecteditems.ProtectedItemResource{ @@ -145,27 +140,23 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource protectionState, ok := d.GetOk("protection_state") protectionStopped := strings.EqualFold(protectionState.(string), string(protecteditems.ProtectionStateProtectionStopped)) requireUpdateProtectionState := ok && protectionStopped - skipNormalUpdate := protectionStopped && !d.IsNewResource() - - // stopped protected item has no `backup_policy_id`, though we can update it before stopping we can not read it. - if !skipNormalUpdate { - resp, err := client.CreateOrUpdate(ctx, id, item) - if err != nil { - return fmt.Errorf("creating/updating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) - } - operationId, err := parseBackupOperationId(resp.HttpResponse) - if err != nil { - return fmt.Errorf("issuing creating/updating request for %s: %+v", id, err) - } + resp, err := client.CreateOrUpdate(ctx, id, item) + if err != nil { + return fmt.Errorf("creating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + } - if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil { - return err - } + operationId, err := parseBackupOperationId(resp.HttpResponse) + if err != nil { + return fmt.Errorf("issuing creating request for %s: %+v", id, err) + } - d.SetId(id.ID()) + if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil { + return err } + d.SetId(id.ID()) + // the protection state will be updated in the additional update. if requireUpdateProtectionState { p := protecteditems.ProtectionState(protectionState.(string)) @@ -176,14 +167,14 @@ func resourceRecoveryServicesBackupProtectedVMCreateUpdate(d *pluginsdk.Resource }, } - resp, err := client.CreateOrUpdate(ctx, id, updateInput) + resp, err = client.CreateOrUpdate(ctx, id, updateInput) if err != nil { - return fmt.Errorf("creating/updating %s: %+v", id, err) + return fmt.Errorf("creating %s: %+v", id, err) } - operationId, err := parseBackupOperationId(resp.HttpResponse) + operationId, err = parseBackupOperationId(resp.HttpResponse) if err != nil { - return fmt.Errorf("issuing creating/updating request for %s: %+v", id, err) + return fmt.Errorf("issuing creating request for %s: %+v", id, err) } if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil { @@ -258,6 +249,102 @@ func resourceRecoveryServicesBackupProtectedVMRead(d *pluginsdk.ResourceData, me return nil } +func resourceRecoveryServicesBackupProtectedVMUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).RecoveryServices.ProtectedItemsClient + opClient := meta.(*clients.Client).RecoveryServices.ProtectedItemOperationResultsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := protecteditems.ParseProtectedItemID(d.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, *id, protecteditems.GetOperationOptions{}) + if err != nil { + return err + } + + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + + if _, ok := existing.Model.Properties.(protecteditems.AzureIaaSComputeVMProtectedItem); !ok { + return fmt.Errorf("retrieving %s: `properties` was not a VM Protected Item", id) + } + + model := *existing.Model + properties := existing.Model.Properties.(protecteditems.AzureIaaSComputeVMProtectedItem) + updateProtectedBackup := false + + if d.HasChange("backup_policy_id") { + properties.PolicyName = pointer.To(d.Get("backup_policy_id").(string)) + updateProtectedBackup = true + } + + if d.HasChange("exclude_disk_luns") || d.HasChange("include_disk_luns") { + properties.ExtendedProperties = expandDiskExclusion(d) + updateProtectedBackup = true + } + + model.Properties = properties + + if updateProtectedBackup { + resp, err := client.CreateOrUpdate(ctx, *id, model) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + operationId, err := parseBackupOperationId(resp.HttpResponse) + if err != nil { + return fmt.Errorf("issuing update request for %s: %+v", id, err) + } + + if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, *id, operationId); err != nil { + return err + } + } + + protectionState := string(pointer.From(properties.ProtectionState)) + protectionStopped := false + if d.HasChange("protection_state") { + protectionState = d.Get("protection_state").(string) + protectionStopped = strings.EqualFold(protectionState, string(protecteditems.ProtectionStateProtectionStopped)) + } + + // the protection state will be updated in the additional update. + if protectionStopped { + p := protecteditems.ProtectionState(protectionState) + vmId := d.Get("source_vm_id").(string) + updateInput := protecteditems.ProtectedItemResource{ + Properties: &protecteditems.AzureIaaSComputeVMProtectedItem{ + ProtectionState: &p, + SourceResourceId: utils.String(vmId), + }, + } + + resp, err := client.CreateOrUpdate(ctx, *id, updateInput) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + operationId, err := parseBackupOperationId(resp.HttpResponse) + if err != nil { + return fmt.Errorf("issuing update request for %s: %+v", id, err) + } + + if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, *id, operationId); err != nil { + return err + } + } + + return resourceRecoveryServicesBackupProtectedVMRead(d, meta) +} + func resourceRecoveryServicesBackupProtectedVMDelete(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).RecoveryServices.ProtectedItemsClient opResultClient := meta.(*clients.Client).RecoveryServices.BackupOperationResultsClient From c8e28f18a35d89f829a5fc5a9724771f615b17b1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 10 Jul 2024 11:16:59 -0700 Subject: [PATCH 2/2] Address review --- .../backup_protected_vm_resource.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/services/recoveryservices/backup_protected_vm_resource.go b/internal/services/recoveryservices/backup_protected_vm_resource.go index 85f4bfe13ffa..9b82df4f3b72 100644 --- a/internal/services/recoveryservices/backup_protected_vm_resource.go +++ b/internal/services/recoveryservices/backup_protected_vm_resource.go @@ -67,7 +67,6 @@ func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, defer cancel() resourceGroup := d.Get("resource_group_name").(string) - vaultName := d.Get("recovery_vault_name").(string) // source_vm_id must be specified at creation time but can be removed during update @@ -78,7 +77,7 @@ func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, vmId := d.Get("source_vm_id").(string) policyId := d.Get("backup_policy_id").(string) - if d.IsNewResource() && policyId == "" { + if policyId == "" { return fmt.Errorf("`backup_policy_id` must be specified during creation") } @@ -91,7 +90,7 @@ func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, protectedItemName := fmt.Sprintf("VM;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroupName, parsedVmId.VirtualMachineName) containerName := fmt.Sprintf("iaasvmcontainer;iaasvmcontainerv2;%s;%s", parsedVmId.ResourceGroupName, parsedVmId.VirtualMachineName) - log.Printf("[DEBUG] Creating/updating Azure Backup Protected VM %s (resource group %q)", protectedItemName, resourceGroup) + log.Printf("[DEBUG] Creating Azure Backup Protected VM %s (resource group %q)", protectedItemName, resourceGroup) id := protecteditems.NewProtectedItemID(subscriptionId, resourceGroup, vaultName, "Azure", containerName, protectedItemName) @@ -143,12 +142,12 @@ func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, resp, err := client.CreateOrUpdate(ctx, id, item) if err != nil { - return fmt.Errorf("creating Azure Backup Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err) + return fmt.Errorf("creating %s: %+v", id, err) } operationId, err := parseBackupOperationId(resp.HttpResponse) if err != nil { - return fmt.Errorf("issuing creating request for %s: %+v", id, err) + return fmt.Errorf("issuing create request for %s: %+v", id, err) } if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil { @@ -174,7 +173,7 @@ func resourceRecoveryServicesBackupProtectedVMCreate(d *pluginsdk.ResourceData, operationId, err = parseBackupOperationId(resp.HttpResponse) if err != nil { - return fmt.Errorf("issuing creating request for %s: %+v", id, err) + return fmt.Errorf("issuing create request for %s: %+v", id, err) } if err = resourceRecoveryServicesBackupProtectedVMWaitForStateCreateUpdate(ctx, opClient, id, operationId); err != nil { @@ -282,7 +281,7 @@ func resourceRecoveryServicesBackupProtectedVMUpdate(d *pluginsdk.ResourceData, updateProtectedBackup := false if d.HasChange("backup_policy_id") { - properties.PolicyName = pointer.To(d.Get("backup_policy_id").(string)) + properties.PolicyId = pointer.To(d.Get("backup_policy_id").(string)) updateProtectedBackup = true }