Skip to content

Commit

Permalink
Merge pull request #1637 from terraform-providers/r-recovery_services…
Browse files Browse the repository at this point in the history
…-backupvm

New Resource: azurerm_recovery_services_protected_vm
  • Loading branch information
katbyte authored Oct 18, 2018
2 parents 6b04bbf + 77f693b commit cf872ea
Show file tree
Hide file tree
Showing 18 changed files with 851 additions and 46 deletions.
5 changes: 5 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ type ArmClient struct {

// Recovery Services
recoveryServicesVaultsClient recoveryservices.VaultsClient
recoveryServicesProtectedItemsClient backup.ProtectedItemsClient
recoveryServicesProtectionPoliciesClient backup.ProtectionPoliciesClient

// Relay
Expand Down Expand Up @@ -1002,6 +1003,10 @@ func (c *ArmClient) registerRecoveryServiceClients(endpoint, subscriptionId stri
c.configureClient(&vaultsClient.Client, auth)
c.recoveryServicesVaultsClient = vaultsClient

protectedItemsClient := backup.NewProtectedItemsClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&protectedItemsClient.Client, auth)
c.recoveryServicesProtectedItemsClient = protectedItemsClient

protectionPoliciesClient := backup.NewProtectionPoliciesClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&protectionPoliciesClient.Client, auth)
c.recoveryServicesProtectionPoliciesClient = protectionPoliciesClient
Expand Down
2 changes: 2 additions & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ func Provider() terraform.ResourceProvider {
"azurerm_public_ip": resourceArmPublicIp(),
"azurerm_relay_namespace": resourceArmRelayNamespace(),
"azurerm_recovery_services_vault": resourceArmRecoveryServicesVault(),
"azurerm_recovery_services_protected_vm": resourceArmRecoveryServicesProtectedVm(),
"azurerm_recovery_services_protection_policy_vm": resourceArmRecoveryServicesProtectionPolicyVm(),
"azurerm_redis_cache": resourceArmRedisCache(),
"azurerm_redis_firewall_rule": resourceArmRedisFirewallRule(),
"azurerm_resource_group": resourceArmResourceGroup(),
Expand Down
224 changes: 224 additions & 0 deletions azurerm/resource_arm_recovery_services_protected_vm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package azurerm

import (
"context"
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/backup"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmRecoveryServicesProtectedVm() *schema.Resource {
return &schema.Resource{
Create: resourceArmRecoveryServicesProtectedVmCreateUpdate,
Read: resourceArmRecoveryServicesProtectedVmRead,
Update: resourceArmRecoveryServicesProtectedVmCreateUpdate,
Delete: resourceArmRecoveryServicesProtectedVmDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{

"resource_group_name": resourceGroupNameSchema(),

"recovery_vault_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringMatch(
regexp.MustCompile("^[a-zA-Z][-a-zA-Z0-9]{1,49}$"),
"Recovery Service Vault name must be 2 - 50 characters long, start with a letter, contain only letters, numbers and hyphens.",
),
},

"source_vm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},

"backup_policy_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},

"tags": tagsSchema(),
},
}
}

func resourceArmRecoveryServicesProtectedVmCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).recoveryServicesProtectedItemsClient
ctx := meta.(*ArmClient).StopContext

resourceGroup := d.Get("resource_group_name").(string)
tags := 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", resourceGroup, vmName)
containerName := fmt.Sprintf("iaasvmcontainer;iaasvmcontainerv2;%s;%s", resourceGroup, vmName)

log.Printf("[DEBUG] Creating/updating Recovery Service Protected VM %s (resource group %q)", protectedItemName, resourceGroup)

item := backup.ProtectedItemResource{
Tags: expandTags(tags),
Properties: &backup.AzureIaaSComputeVMProtectedItem{
PolicyID: &policyId,
ProtectedItemType: backup.ProtectedItemType(backup.ProtectedItemTypeMicrosoftClassicComputevirtualMachines),
WorkloadType: backup.VM,
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 Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}

resp, err := resourceArmRecoveryServicesProtectedVmWaitForState(client, ctx, true, vaultName, resourceGroup, containerName, protectedItemName)
if err != nil {
return err
}
id := strings.Replace(*resp.ID, "Subscriptions", "subscriptions", 1)
d.SetId(id)

return resourceArmRecoveryServicesProtectedVmRead(d, meta)
}

func resourceArmRecoveryServicesProtectedVmRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).recoveryServicesProtectedItemsClient
ctx := meta.(*ArmClient).StopContext

id, err := 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 Recovery Service 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 Recovery Service 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))
}
}
}

flattenAndSetTags(d, resp.Tags)

return nil
}

func resourceArmRecoveryServicesProtectedVmDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).recoveryServicesProtectedItemsClient
ctx := meta.(*ArmClient).StopContext

id, err := 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 Recovery Service 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 Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}
}

if _, err := resourceArmRecoveryServicesProtectedVmWaitForState(client, ctx, false, vaultName, resourceGroup, containerName, protectedItemName); err != nil {
return err
}

return nil
}

func resourceArmRecoveryServicesProtectedVmWaitForState(client backup.ProtectedItemsClient, ctx context.Context, found bool, vaultName, resourceGroup, containerName, protectedItemName string) (backup.ProtectedItemResource, error) {
state := &resource.StateChangeConf{
Timeout: 30 * time.Minute,
MinTimeout: 30 * time.Second,
Delay: 10 * time.Second,
Refresh: 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 Recovery Service Protected VM %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
}

return resp, "Found", nil
},
}

if found {
state.Pending = []string{"NotFound"}
state.Target = []string{"Found"}
} else {
state.Pending = []string{"Found"}
state.Target = []string{"NotFound"}
}

resp, err := state.WaitForState()
if err != nil {
return resp.(backup.ProtectedItemResource), fmt.Errorf("Error waiting for the Recovery Service Protected VM %q to be %t (Resource Group %q) to provision: %+v", protectedItemName, found, resourceGroup, err)
}

return resp.(backup.ProtectedItemResource), nil
}
Loading

0 comments on commit cf872ea

Please sign in to comment.