Skip to content

Commit

Permalink
Fix azurerm_backup_protected_file_share (#9015)
Browse files Browse the repository at this point in the history
azurerm_backup_protected_file_share was broken due to a backward incompatible change of the Azure Backup backend, using system names instead of user defined names.

Fixes #9368
  • Loading branch information
katbyte authored Jan 14, 2021
2 parents 00c602b + 102382d commit d15e39f
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ resource "azurerm_recovery_services_vault" "testvlt" {
resource_group_name = azurerm_resource_group.test.name
sku = "Standard"
soft_delete_enabled = false
soft_delete_enabled = true
}
resource "azurerm_storage_account" "test" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ func resourceBackupProtectedFileShare() *schema.Resource {
}

func resourceBackupProtectedFileShareCreateUpdate(d *schema.ResourceData, meta interface{}) error {
protectedClient := meta.(*clients.Client).RecoveryServices.ProtectedItemsGroupClient
protectableClient := meta.(*clients.Client).RecoveryServices.ProtectableItemsClient
client := meta.(*clients.Client).RecoveryServices.ProtectedItemsClient
opClient := meta.(*clients.Client).RecoveryServices.BackupOperationStatusesClient
ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d)
Expand All @@ -93,16 +95,62 @@ func resourceBackupProtectedFileShareCreateUpdate(d *schema.ResourceData, meta i
return fmt.Errorf("[ERROR] parsed source_storage_account_id '%s' doesn't contain 'storageAccounts'", storageAccountID)
}

protectedItemName := fmt.Sprintf("AzureFileShare;%s", fileShareName)
containerName := fmt.Sprintf("StorageContainer;storage;%s;%s", parsedStorageAccountID.ResourceGroup, accountName)
// the fileshare has a user defined name, but its system name (fileShareSystemName) is only known to Azure Backup
fileShareSystemName := ""
// @aristosvo: preferred filter would be like below but the 'and' expression seems to fail
// filter := fmt.Sprintf("backupManagementType eq 'AzureStorage' and friendlyName eq '%s'", fileShareName)
// this means which means we have to do it client side and loop over backupProtectedItems en backupProtectableItems until share is found
filter := "backupManagementType eq 'AzureStorage'"
backupProtectableItemsResponse, err := protectableClient.List(ctx, vaultName, resourceGroup, filter, "")
if err != nil {
return fmt.Errorf("Error checking for protectable fileshares in Recovery Service Vault %q (Resource Group %q): %+v", vaultName, resourceGroup, err)
}

for _, protectableItem := range backupProtectableItemsResponse.Values() {
if *protectableItem.Name == "" || protectableItem.Properties == nil {
continue
}
azureFileShareProtectableItem, check := protectableItem.Properties.AsAzureFileShareProtectableItem()

log.Printf("[DEBUG] Creating/updating Recovery Service Protected File Share %q (Container Name %q)", protectedItemName, containerName)
// check if protected item has the same fileshare name and is from the same storage account
if check && *azureFileShareProtectableItem.FriendlyName == fileShareName && *azureFileShareProtectableItem.ParentContainerFriendlyName == accountName {
fileShareSystemName = *protectableItem.Name
break
}
}

// fileShareSystemName not found? Check if already protected by this vault!
if fileShareSystemName == "" {
backupProtectedItemsResponse, err := protectedClient.List(ctx, vaultName, resourceGroup, filter, "")
if err != nil {
return fmt.Errorf("Error checking for protected fileshares in Recovery Service Vault %q (Resource Group %q): %+v", vaultName, resourceGroup, err)
}

for _, protectedItem := range backupProtectedItemsResponse.Values() {
if *protectedItem.Name == "" || protectedItem.Properties == nil {
continue
}
azureFileShareProtectedItem, check := protectedItem.Properties.AsAzureFileshareProtectedItem()

// check if protected item has the same fileshare name and is from the same storage account
if check && *azureFileShareProtectedItem.FriendlyName == fileShareName && strings.EqualFold(*azureFileShareProtectedItem.SourceResourceID, storageAccountID) {
fileShareSystemName = *protectedItem.Name
break
}
}
}
if fileShareSystemName == "" {
return fmt.Errorf("[ERROR] fileshare '%s' not found in protectable or protected fileshares, make sure Storage Account %q is registered with Recovery Service Vault %q (Resource Group %q)", fileShareName, accountName, vaultName, resourceGroup)
}

containerName := fmt.Sprintf("StorageContainer;storage;%s;%s", parsedStorageAccountID.ResourceGroup, accountName)
log.Printf("[DEBUG] creating/updating Recovery Service Protected File Share %q (Container Name %q)", fileShareName, containerName)

if d.IsNewResource() {
existing, err2 := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "")
existing, err2 := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, fileShareSystemName, "")
if err2 != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing Recovery Service Protected File Share %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err2)
return fmt.Errorf("Error checking for presence of existing Recovery Service Protected File Share %q (Resource Group %q): %+v", fileShareName, resourceGroup, err2)
}
}

Expand All @@ -121,9 +169,9 @@ func resourceBackupProtectedFileShareCreateUpdate(d *schema.ResourceData, meta i
},
}

resp, err := client.CreateOrUpdate(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, item)
resp, err := client.CreateOrUpdate(ctx, vaultName, resourceGroup, "Azure", containerName, fileShareSystemName, item)
if err != nil {
return fmt.Errorf("Error creating/updating Recovery Service Protected File Share %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
return fmt.Errorf("Error creating/updating Recovery Service Protected File Share %q (Resource Group %q): %+v", fileShareName, resourceGroup, err)
}

locationURL, err := resp.Response.Location()
Expand All @@ -143,10 +191,10 @@ func resourceBackupProtectedFileShareCreateUpdate(d *schema.ResourceData, meta i
return err
}

resp, err = client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "")
resp, err = client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, fileShareSystemName, "")

if err != nil {
return fmt.Errorf("Error creating/udpating Azure File Share backup item %q (Vault %q): %+v", protectedItemName, vaultName, err)
return fmt.Errorf("Error creating/updating Azure File Share backup item %q (Vault %q): %+v", fileShareName, vaultName, 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
Expand All @@ -165,21 +213,21 @@ func resourceBackupProtectedFileShareRead(d *schema.ResourceData, meta interface
return err
}

protectedItemName := id.Path["protectedItems"]
fileShareSystemName := id.Path["protectedItems"]
vaultName := id.Path["vaults"]
resourceGroup := id.ResourceGroup
containerName := id.Path["protectionContainers"]

log.Printf("[DEBUG] Reading Recovery Service Protected File Share %q (resource group %q)", protectedItemName, resourceGroup)
log.Printf("[DEBUG] Reading Recovery Service Protected File Share %q (resource group %q)", fileShareSystemName, resourceGroup)

resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName, "")
resp, err := client.Get(ctx, vaultName, resourceGroup, "Azure", containerName, fileShareSystemName, "")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
d.SetId("")
return nil
}

return fmt.Errorf("Error making Read request on Recovery Service Protected File Share %q (Vault %q Resource Group %q): %+v", protectedItemName, vaultName, resourceGroup, err)
return fmt.Errorf("Error making Read request on Recovery Service Protected File Share %q (Vault %q Resource Group %q): %+v", fileShareSystemName, vaultName, resourceGroup, err)
}

d.Set("resource_group_name", resourceGroup)
Expand Down Expand Up @@ -211,17 +259,17 @@ func resourceBackupProtectedFileShareDelete(d *schema.ResourceData, meta interfa
return err
}

protectedItemName := id.Path["protectedItems"]
fileShareSystemName := 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)
log.Printf("[DEBUG] Deleting Recovery Service Protected Item %q (resource group %q)", fileShareSystemName, resourceGroup)

resp, err := client.Delete(ctx, vaultName, resourceGroup, "Azure", containerName, protectedItemName)
resp, err := client.Delete(ctx, vaultName, resourceGroup, "Azure", containerName, fileShareSystemName)
if err != nil {
if !utils.ResponseWasNotFound(resp) {
return fmt.Errorf("Error issuing delete request for Recovery Service Protected File Share %q (Resource Group %q): %+v", protectedItemName, resourceGroup, err)
return fmt.Errorf("Error issuing delete request for Recovery Service Protected File Share %q (Resource Group %q): %+v", fileShareSystemName, resourceGroup, err)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ func TestAccBackupProtectedFileShare_basic(t *testing.T) {
})
}

func TestAccBackupProtectedFileShare_multiple(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_backup_protected_file_share", "test")
r := BackupProtectedFileShareResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.multiple(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("resource_group_name").Exists(),
),
},
data.ImportStep(),
{
// vault cannot be deleted unless we unregister all backups
Config: r.baseMultiple(data),
},
})
}

func TestAccBackupProtectedFileShare_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_backup_protected_file_share", "test")
r := BackupProtectedFileShareResource{}
Expand Down Expand Up @@ -142,7 +162,7 @@ resource "azurerm_recovery_services_vault" "test" {
resource_group_name = "${azurerm_resource_group.test.name}"
sku = "Standard"
soft_delete_enabled = false
soft_delete_enabled = true
}
resource "azurerm_backup_policy_file_share" "test1" {
Expand Down Expand Up @@ -224,9 +244,128 @@ func (r BackupProtectedFileShareResource) requiresImport(data acceptance.TestDat
resource "azurerm_backup_protected_file_share" "test_import" {
resource_group_name = azurerm_resource_group.test.name
recovery_vault_name = azurerm_recovery_services_vault.test.name
source_storage_account_id = azurerm_storage_account.test.id
source_storage_account_id = azurerm_backup_container_storage_account.test.storage_account_id
source_file_share_name = azurerm_storage_share.test.name
backup_policy_id = azurerm_backup_policy_file_share.test1.id
}
`, r.base(data))
`, r.basic(data))
}

func (r BackupProtectedFileShareResource) baseMultiple(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "test" {
name = "acctestRG-backup-%[1]d"
location = "%[2]s"
}
resource "azurerm_storage_account" "test1" {
name = "acctest%[3]s1"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account" "test2" {
name = "acctest%[3]s2"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_share" "testshare1" {
name = "acctest-ss-%[1]d-1"
storage_account_name = "${azurerm_storage_account.test1.name}"
metadata = {}
lifecycle {
ignore_changes = [metadata] // Ignore changes Azure Backup makes to the metadata
}
}
resource "azurerm_storage_share" "testshare2" {
name = "acctest-ss-%[1]d-2"
storage_account_name = "${azurerm_storage_account.test1.name}"
metadata = {}
lifecycle {
ignore_changes = [metadata] // Ignore changes Azure Backup makes to the metadata
}
}
resource "azurerm_storage_share" "testshare3" {
name = "acctest-ss-%[1]d-1"
storage_account_name = "${azurerm_storage_account.test2.name}"
metadata = {}
lifecycle {
ignore_changes = [metadata] // Ignore changes Azure Backup makes to the metadata
}
}
resource "azurerm_storage_share" "testshare4" {
name = "acctest-ss-%[1]d-2"
storage_account_name = "${azurerm_storage_account.test2.name}"
metadata = {}
lifecycle {
ignore_changes = [metadata] // Ignore changes Azure Backup makes to the metadata
}
}
resource "azurerm_recovery_services_vault" "test" {
name = "acctest-VAULT-%[1]d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
sku = "Standard"
soft_delete_enabled = true
}
resource "azurerm_backup_policy_file_share" "test" {
name = "acctest-PFS-%[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
}
}
`, data.RandomInteger, data.Locations.Primary, data.RandomString)
}

func (r BackupProtectedFileShareResource) multiple(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
resource "azurerm_backup_container_storage_account" "test1" {
resource_group_name = azurerm_resource_group.test.name
recovery_vault_name = azurerm_recovery_services_vault.test.name
storage_account_id = azurerm_storage_account.test1.id
}
resource "azurerm_backup_container_storage_account" "test2" {
resource_group_name = azurerm_resource_group.test.name
recovery_vault_name = azurerm_recovery_services_vault.test.name
storage_account_id = azurerm_storage_account.test2.id
}
resource "azurerm_backup_protected_file_share" "test" {
resource_group_name = azurerm_resource_group.test.name
recovery_vault_name = azurerm_recovery_services_vault.test.name
source_storage_account_id = azurerm_backup_container_storage_account.test2.storage_account_id
source_file_share_name = azurerm_storage_share.testshare3.name
backup_policy_id = azurerm_backup_policy_file_share.test.id
}
`, r.baseMultiple(data))
}
10 changes: 10 additions & 0 deletions azurerm/internal/services/recoveryservices/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

type Client struct {
ProtectableItemsClient *backup.ProtectableItemsClient
ProtectedItemsClient *backup.ProtectedItemsClient
ProtectedItemsGroupClient *backup.ProtectedItemsGroupClient
ProtectionPoliciesClient *backup.ProtectionPoliciesClient
BackupProtectionContainersClient *backup.ProtectionContainersClient
BackupOperationStatusesClient *backup.OperationStatusesClient
Expand All @@ -29,9 +31,15 @@ func NewClient(o *common.ClientOptions) *Client {
vaultsClient := recoveryservices.NewVaultsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&vaultsClient.Client, o.ResourceManagerAuthorizer)

protectableItemsClient := backup.NewProtectableItemsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&protectableItemsClient.Client, o.ResourceManagerAuthorizer)

protectedItemsClient := backup.NewProtectedItemsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&protectedItemsClient.Client, o.ResourceManagerAuthorizer)

protectedItemsGroupClient := backup.NewProtectedItemsGroupClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&protectedItemsGroupClient.Client, o.ResourceManagerAuthorizer)

protectionPoliciesClient := backup.NewProtectionPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId)
o.ConfigureClient(&protectionPoliciesClient.Client, o.ResourceManagerAuthorizer)

Expand Down Expand Up @@ -78,7 +86,9 @@ func NewClient(o *common.ClientOptions) *Client {
}

return &Client{
ProtectableItemsClient: &protectableItemsClient,
ProtectedItemsClient: &protectedItemsClient,
ProtectedItemsGroupClient: &protectedItemsGroupClient,
ProtectionPoliciesClient: &protectionPoliciesClient,
BackupProtectionContainersClient: &backupProtectionContainersClient,
BackupOperationStatusesClient: &backupOperationStatusesClient,
Expand Down
8 changes: 2 additions & 6 deletions website/docs/r/backup_protected_file_share.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ description: |-

Manages an Azure Backup Protected File Share to enable backups for file shares within an Azure Storage Account

-> **NOTE:** Azure Backup for Azure File Shares is currently in public preview. During the preview, the service is subject to additional limitations and unsupported backup scenarios. [Read More](https://docs.microsoft.com/en-us/azure/backup/backup-azure-files#limitations-for-azure-file-share-backup-during-preview)

-> **NOTE** Azure Backup for Azure File Shares does not support Soft Delete at this time. Deleting this resource will also delete all associated backup data. Please exercise caution. Consider using [`prevent_destroy`](https://www.terraform.io/docs/configuration/resources.html#prevent_destroy) to guard against accidental deletion.

## Example Usage

```hcl
Expand Down Expand Up @@ -108,7 +104,7 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d
Azure Backup Protected File Shares can be imported using the `resource id`, e.g.

```shell
terraform import azurerm_backup_protected_file_share.item1 "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.RecoveryServices/vaults/example-recovery-vault/backupFabrics/Azure/protectionContainers/StorageContainer;storage;group2;example-storage-account/protectedItems/AzureFileShare;example-share"
terraform import azurerm_backup_protected_file_share.item1 "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.RecoveryServices/vaults/example-recovery-vault/backupFabrics/Azure/protectionContainers/StorageContainer;storage;group2;example-storage-account/protectedItems/AzureFileShare;3f6e3108a45793581bcbd1c61c87a3b2ceeb4ff4bc02a95ce9d1022b23722935"
```

Note the ID requires quoting as there are semicolons
-> **NOTE** The ID requires quoting as there are semicolons. This user unfriendly ID can be found in the Deployments of the used resourcegroup, look for an Deployment which starts with `ConfigureAFSProtection-`, click then `Go to resource`.

0 comments on commit d15e39f

Please sign in to comment.