Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Azure File Share backup #5213

Merged
merged 18 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions azurerm/internal/services/recoveryservices/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import (
)

type Client struct {
ProtectedItemsClient *backup.ProtectedItemsClient
ProtectionPoliciesClient *backup.ProtectionPoliciesClient
VaultsClient *recoveryservices.VaultsClient
FabricClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationFabricsClient
ProtectionContainerClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainersClient
ReplicationPoliciesClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationPoliciesClient
ContainerMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainerMappingsClient
NetworkMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationNetworkMappingsClient
ReplicationMigrationItemsClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectedItemsClient
ProtectedItemsClient *backup.ProtectedItemsClient
ProtectionPoliciesClient *backup.ProtectionPoliciesClient
BackupProtectionContainersClient *backup.ProtectionContainersClient
BackupOperationStatusesClient *backup.OperationStatusesClient
VaultsClient *recoveryservices.VaultsClient
FabricClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationFabricsClient
ProtectionContainerClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainersClient
ReplicationPoliciesClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationPoliciesClient
ContainerMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectionContainerMappingsClient
NetworkMappingClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationNetworkMappingsClient
ReplicationMigrationItemsClient func(resourceGroupName string, vaultName string) siterecovery.ReplicationProtectedItemsClient
}

func NewClient(o *common.ClientOptions) *Client {
Expand All @@ -29,6 +31,12 @@ func NewClient(o *common.ClientOptions) *Client {
protectionPoliciesClient := backup.NewProtectionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&protectionPoliciesClient.Client, o.ResourceManagerAuthorizer)

backupProtectionContainersClient := backup.NewProtectionContainersClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&backupProtectionContainersClient.Client, o.ResourceManagerAuthorizer)

backupOperationStatusesClient := backup.NewOperationStatusesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&backupOperationStatusesClient.Client, o.ResourceManagerAuthorizer)

fabricClient := func(resourceGroupName string, vaultName string) siterecovery.ReplicationFabricsClient {
client := siterecovery.NewReplicationFabricsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId, resourceGroupName, vaultName)
o.ConfigureClient(&client.Client, o.ResourceManagerAuthorizer)
Expand Down Expand Up @@ -66,14 +74,16 @@ func NewClient(o *common.ClientOptions) *Client {
}

return &Client{
ProtectedItemsClient: &protectedItemsClient,
ProtectionPoliciesClient: &protectionPoliciesClient,
VaultsClient: &vaultsClient,
FabricClient: fabricClient,
ProtectionContainerClient: protectionContainerClient,
ReplicationPoliciesClient: replicationPoliciesClient,
ContainerMappingClient: containerMappingClient,
NetworkMappingClient: networkMappingClient,
ReplicationMigrationItemsClient: replicationMigrationItemsClient,
ProtectedItemsClient: &protectedItemsClient,
ProtectionPoliciesClient: &protectionPoliciesClient,
BackupProtectionContainersClient: &backupProtectionContainersClient,
BackupOperationStatusesClient: &backupOperationStatusesClient,
VaultsClient: &vaultsClient,
FabricClient: fabricClient,
ProtectionContainerClient: protectionContainerClient,
ReplicationPoliciesClient: replicationPoliciesClient,
ContainerMappingClient: containerMappingClient,
NetworkMappingClient: networkMappingClient,
ReplicationMigrationItemsClient: replicationMigrationItemsClient,
}
}
3 changes: 3 additions & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ func Provider() terraform.ResourceProvider {
"azurerm_azuread_application": resourceArmActiveDirectoryApplication(),
"azurerm_azuread_service_principal_password": resourceArmActiveDirectoryServicePrincipalPassword(),
"azurerm_azuread_service_principal": resourceArmActiveDirectoryServicePrincipal(),
"azurerm_backup_container_storage_account": resourceArmBackupProtectionContainerStorageAccount(),
"azurerm_backup_policy_file_share": resourceArmBackupProtectionPolicyFileShare(),
"azurerm_backup_protected_file_share": resourceArmBackupProtectedFileShare(),
"azurerm_bastion_host": resourceArmBastionHost(),
"azurerm_batch_account": resourceArmBatchAccount(),
"azurerm_batch_application": resourceArmBatchApplication(),
Expand Down
250 changes: 250 additions & 0 deletions azurerm/resource_arm_backup_container_storage_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package azurerm

import (
"context"
"fmt"
"log"
"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/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmBackupProtectionContainerStorageAccount() *schema.Resource {
return &schema.Resource{
Create: resourceArmBackupProtectionContainerStorageAccountCreate,
Read: resourceArmBackupProtectionContainerStorageAccountRead,
Update: nil,
Delete: resourceArmBackupProtectionContainerStorageAccountDelete,
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{
"resource_group_name": azure.SchemaResourceGroupName(),

"recovery_vault_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateRecoveryServicesVaultName,
},
"storage_account_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},
},
}
}

func resourceArmBackupProtectionContainerStorageAccountCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).RecoveryServices.BackupProtectionContainersClient
opStatusClient := meta.(*ArmClient).RecoveryServices.BackupOperationStatusesClient
ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d)
defer cancel()

resGroup := d.Get("resource_group_name").(string)
vaultName := d.Get("recovery_vault_name").(string)
storageAccountID := d.Get("storage_account_id").(string)

parsedStorageAccountID, err := azure.ParseAzureResourceID(storageAccountID)
if err != nil {
return fmt.Errorf("[ERROR] Unable to parse storage_account_id '%s': %+v", storageAccountID, err)
}
accountName, hasName := parsedStorageAccountID.Path["storageAccounts"]
if !hasName {
return fmt.Errorf("[ERROR] parsed storage_account_id '%s' doesn't contain 'storageAccounts'", storageAccountID)
}

containerName := fmt.Sprintf("StorageContainer;storage;%s;%s", parsedStorageAccountID.ResourceGroup, accountName)

if features.ShouldResourcesBeImported() && d.IsNewResource() {
existing, err := client.Get(ctx, vaultName, resGroup, "Azure", containerName)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing recovery services protection container %s (Vault %s): %+v", containerName, vaultName, err)
}
}

if existing.ID != nil && *existing.ID != "" {
return tf.ImportAsExistsError("azurerm_backup_protection_container_storage", azure.HandleAzureSdkForGoBug2824(*existing.ID))
}
}

parameters := backup.ProtectionContainerResource{
Properties: &backup.AzureStorageContainer{
SourceResourceID: &storageAccountID,
FriendlyName: &accountName,
BackupManagementType: backup.ManagementTypeAzureStorage,
ContainerType: backup.ContainerTypeStorageContainer1,
},
}

resp, err := client.Register(ctx, vaultName, resGroup, "Azure", containerName, parameters)
if err != nil {
return fmt.Errorf("Error registering backup protection container %s (Vault %s): %+v", containerName, vaultName, err)
}

locationURL, err := resp.Response.Location() // Operation ID found in the Location header
if locationURL == nil || err != nil {
return fmt.Errorf("Unable to determine operation URL for protection container registration status for %s. (Vault %s): Location header missing or empty", containerName, vaultName)
}

opResourceID := azure.HandleAzureSdkForGoBug2824(locationURL.Path)

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

operationID := parsedLocation.Path["operationResults"]
if _, err = resourceArmBackupProtectionContainerStorageAccountWaitForOperation(ctx, opStatusClient, vaultName, resGroup, operationID, d); err != nil {
return err
}

resp, err = client.Get(ctx, vaultName, resGroup, "Azure", containerName)
if err != nil {
return fmt.Errorf("Error retrieving site recovery protection container %s (Vault %s): %+v", containerName, vaultName, err)
}

d.SetId(azure.HandleAzureSdkForGoBug2824(*resp.ID))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the HandleAzureSdkForGoBug2824 isn't required anymore (the service team claims its been fixed Azure/azure-sdk-for-go#2824 (comment))


return resourceArmBackupProtectionContainerStorageAccountRead(d, meta)
}

func resourceArmBackupProtectionContainerStorageAccountRead(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["backupFabrics"]
containerName := id.Path["protectionContainers"]

client := meta.(*ArmClient).RecoveryServices.BackupProtectionContainersClient
ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d)
defer cancel()

resp, err := client.Get(ctx, vaultName, resGroup, fabricName, containerName)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
return nil
}
return fmt.Errorf("Error making Read request on backup protection container %s (Vault %s): %+v", containerName, vaultName, err)
}

d.Set("resource_group_name", resGroup)
d.Set("recovery_vault_name", vaultName)

if properties, ok := resp.Properties.AsAzureStorageContainer(); ok && properties != nil {
d.Set("storage_account_id", properties.SourceResourceID)
}

return nil
}

func resourceArmBackupProtectionContainerStorageAccountDelete(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["backupFabrics"]
containerName := id.Path["protectionContainers"]

client := meta.(*ArmClient).RecoveryServices.BackupProtectionContainersClient
opClient := meta.(*ArmClient).RecoveryServices.BackupOperationStatusesClient
ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d)
defer cancel()

resp, err := client.Unregister(ctx, vaultName, resGroup, fabricName, containerName)
if err != nil {
return fmt.Errorf("Error deregistering backup protection container %s (Vault %s): %+v", containerName, vaultName, err)
}

locationURL, err := resp.Response.Location()
if err != nil || locationURL == nil {
return fmt.Errorf("Error unregistering backup protection container %s (Vault %s): Location header missing or empty", containerName, vaultName)
}

opResourceID := azure.HandleAzureSdkForGoBug2824(locationURL.Path)

parsedLocation, err := azure.ParseAzureResourceID(opResourceID)
if err != nil {
return err
}
operationID := parsedLocation.Path["backupOperationResults"]

if _, err = resourceArmBackupProtectionContainerStorageAccountWaitForOperation(ctx, opClient, vaultName, resGroup, operationID, d); err != nil {
return err
}

return nil
}

func resourceArmBackupProtectionContainerStorageAccountWaitForOperation(ctx context.Context, client *backup.OperationStatusesClient, vaultName, resourceGroup, operationID string, d *schema.ResourceData) (backup.OperationStatus, error) {
state := &resource.StateChangeConf{
MinTimeout: 10 * time.Second,
Delay: 10 * time.Second,
Pending: []string{"InProgress"},
Target: []string{"Succeeded"},
Refresh: resourceArmBackupProtectionContainerStorageAccountCheckOperation(ctx, client, vaultName, resourceGroup, operationID),
ContinuousTargetOccurence: 5, // Without this buffer, file share backups and storage account deletions may fail if performed immediately after creating/destroying the container
}

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
}

log.Printf("[DEBUG] Waiting for backup container operation %q (Vault %q) to complete", operationID, vaultName)
resp, err := state.WaitForState()
if err != nil {
return resp.(backup.OperationStatus), err
}
return resp.(backup.OperationStatus), nil
}

func resourceArmBackupProtectionContainerStorageAccountCheckOperation(ctx context.Context, client *backup.OperationStatusesClient, vaultName, resourceGroup, operationID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := client.Get(ctx, vaultName, resourceGroup, operationID)
if err != nil {
return resp, "Error", fmt.Errorf("Error making Read request on Recovery Service Protection Container operation %q (Vault %q in Resource Group %q): %+v", operationID, vaultName, resourceGroup, err)
}

if opErr := resp.Error; opErr != nil {
errMsg := "No upstream error message"
if opErr.Message != nil {
errMsg = *opErr.Message
}
err = fmt.Errorf("Recovery Service Protection Container operation status failed with status %q (Vault %q Resource Group %q Operation ID %q): %+v", resp.Status, vaultName, resourceGroup, operationID, errMsg)
}

return resp, string(resp.Status), err
}
}
Loading