Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Release History

* Fix #33183: `az cloud set`: Typo correction on AZURE_BLEU_CLOUD active directory endpoint

**Backup**

* `az backup protection undelete`: Add support for rehydrating soft-deleted Azure File Share backup items (`--backup-management-type AzureStorage --workload-type AzureFileShare`)
* `az backup item list`: Add `--is-deleted` flag to return only soft-deleted backup items in the vault

**Compute**

* `az sig create`: Add new argument group "Managed Service Identity" to configure the service identity of a Shared Image Gallery (SIG) (#33137)
Expand Down
4 changes: 4 additions & 0 deletions src/azure-cli/azure/cli/command_modules/backup/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
- name: List all backed up items within a container. (autogenerated)
text: az backup item list --resource-group MyResourceGroup --vault-name MyVault
crafted: true
- name: List only soft-deleted backup items in the vault. Soft-deleted items can be rehydrated with "az backup protection undelete".
text: az backup item list --resource-group MyResourceGroup --vault-name MyVault --backup-management-type AzureStorage --workload-type AzureFileShare --is-deleted
"""

helps['backup item set-policy'] = """
Expand Down Expand Up @@ -292,6 +294,8 @@
examples:
- name: Rehydrate an item from softdeleted state to stop protection with retained data state.
text: az backup protection undelete --container-name MyContainer --item-name MyItem --resource-group MyResourceGroup --vault-name MyVault --backup-management-type AzureIaasVM --workload-type VM
- name: Rehydrate a soft-deleted Azure File Share item. The item moves to "ProtectionStopped" state and recovery points are retained until soft-delete is disabled or the item is explicitly removed.
text: az backup protection undelete --container-name MyStorageAccount --item-name MyFileShare --resource-group MyResourceGroup --vault-name MyVault --backup-management-type AzureStorage --workload-type AzureFileShare
"""

helps['backup protection enable-for-azurewl'] = """
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli/azure/cli/command_modules/backup/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ def load_arguments(self, _):
c.argument('backup_management_type', extended_backup_management_type)
c.argument('workload_type', workload_type)
c.argument('use_secondary_region', action='store_true', help='Use this flag to list items in secondary region.')
c.argument('is_deleted', action='store_true', help='Use this flag to list only soft-deleted backup items in the vault. '
'These items can be rehydrated using "az backup protection undelete" while still in the soft-delete retention window.')

# Policy
with self.argument_context('backup policy') as c:
Expand Down
16 changes: 16 additions & 0 deletions src/azure-cli/azure/cli/command_modules/backup/custom_afs.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,22 @@ def disable_protection(cmd, client, resource_group_name, vault_name, item,
return helper.track_backup_job(cmd.cli_ctx, result, vault_name, resource_group_name)


def undelete_protection(cmd, client, resource_group_name, vault_name, item):
container_uri = helper.get_protection_container_uri_from_id(item.id)
item_uri = helper.get_protected_item_uri_from_id(item.id)

afs_item_properties = AzureFileshareProtectedItem()
afs_item_properties.policy_id = ''
afs_item_properties.protection_state = ProtectionState.protection_stopped
afs_item_properties.source_resource_id = item.properties.source_resource_id
afs_item_properties.is_rehydrate = True
afs_item = ProtectedItemResource(properties=afs_item_properties)

result = client.create_or_update(vault_name, resource_group_name, fabric_name,
container_uri, item_uri, afs_item, cls=helper.get_pipeline_response)
return helper.track_backup_job(cmd.cli_ctx, result, vault_name, resource_group_name)


def resume_protection(cmd, client, resource_group_name, vault_name, item, policy):
return update_policy_for_item(cmd, client, resource_group_name, vault_name, item, policy)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def show_item(cmd, client, resource_group_name, vault_name, container_name, name


def list_items(cmd, client, resource_group_name, vault_name, workload_type=None, container_name=None,
backup_management_type=None, use_secondary_region=None):
backup_management_type=None, use_secondary_region=None, is_deleted=None):
return common.list_items(cmd, client, resource_group_name, vault_name, workload_type,
container_name, backup_management_type, use_secondary_region)
container_name, backup_management_type, use_secondary_region, is_deleted)


def show_recovery_point(cmd, client, resource_group_name, vault_name, container_name, item_name, name,
Expand Down Expand Up @@ -630,6 +630,9 @@ def undelete_protection(cmd, client, resource_group_name, vault_name, container_
if item.properties.backup_management_type.lower() == "azureiaasvm":
return custom.undelete_protection(cmd, client, resource_group_name, vault_name, item)

if item.properties.backup_management_type.lower() == "azurestorage":
return custom_afs.undelete_protection(cmd, client, resource_group_name, vault_name, item)

if item.properties.backup_management_type.lower() == "azureworkload":
return custom_wl.undelete_protection(cmd, client, resource_group_name, vault_name, item)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def show_item(cmd, client, resource_group_name, vault_name, container_name, name


def list_items(cmd, client, resource_group_name, vault_name, workload_type=None, container_name=None,
container_type=None, use_secondary_region=None):
container_type=None, use_secondary_region=None, is_deleted=None):
workload_type = _check_map(workload_type, workload_type_map)
filter_string = custom_help.get_filter_string({
'backupManagementType': container_type,
Expand All @@ -159,6 +159,10 @@ def list_items(cmd, client, resource_group_name, vault_name, workload_type=None,
items = client.list(vault_name, resource_group_name, filter_string)
paged_items = custom_help.get_list_from_paged_response(items)

if is_deleted:
paged_items = [item for item in paged_items
if getattr(item.properties, 'is_scheduled_for_deferred_delete', None)]

if container_name:
if custom_help.is_native_name(container_name):
return [item for item in paged_items if
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,73 @@ def test_afs_backup_vaultstandard_item(self, resource_group, vault_name, storage
self.cmd('backup protection disable -g {rg} -v {vault} -c {container} -i {item2} --backup-management-type AzureStorage --delete-backup-data true --yes').get_output_in_json()
# self.cmd('backup container unregister -g {rg} -v {vault} -c {container} --yes --backup-management-type AzureStorage')
# time.sleep(100)


@live_only()
@AllowLargeResponse()
@ResourceGroupPreparer(name_prefix="AzureBackupRG_clitest_", location="eastus2euap", random_name_length=32)
@VaultPreparer()
@StorageAccountPreparer(location="eastus2euap")
@FileSharePreparer()
@AFSPolicyPreparer()
@AFSItemPreparer()
def test_afs_backup_softdelete(self, resource_group, vault_name, storage_account, afs_name, policy_name):
"""Validates the soft-delete lifecycle for Azure File Share backup items.

Steps mirror test_backup_softdelete (test_backup_commands.py) for IaaS VM:
1. Disable AFS protection with --delete-backup-data so the item is moved to
soft-deleted state.
2. Confirm `backup item show` reports ProtectionStopped + isScheduled-
ForDeferredDelete=True.
3. Confirm the new `--is-deleted` filter on `backup item list` includes the
soft-deleted item.
4. Run `backup protection undelete` to rehydrate.
5. Confirm `backup item show` reports ProtectionStopped + isScheduled-
ForDeferredDelete=None (item is rehydrated, recovery points retained).
"""
self.kwargs.update({
'vault': vault_name,
'item': afs_name,
'container': storage_account,
'rg': resource_group,
})

# 1. Disable protection with delete-backup-data => soft delete
self.cmd('backup protection disable -g {rg} -v {vault} -c {container} -i {item} '
'--backup-management-type AzureStorage --delete-backup-data true --yes', checks=[
self.check("properties.operation", "DeleteBackupData"),
self.check("properties.status", "Completed"),
self.check("resourceGroup", '{rg}')
])

# 2. Item is now soft-deleted
self.cmd('backup item show -g {rg} -v {vault} -c {container} -n {item} '
'--backup-management-type AzureStorage --workload-type AzureFileShare', checks=[
self.check("properties.friendlyName", '{item}'),
self.check("properties.protectionState", "ProtectionStopped"),
self.check("properties.isScheduledForDeferredDelete", True),
])

# 3. --is-deleted should return the soft-deleted item
self.cmd('backup item list -g {rg} -v {vault} --backup-management-type AzureStorage '
'--workload-type AzureFileShare --is-deleted', checks=[
self.check("length([?properties.friendlyName == '{item}'])", 1),
self.check("[?properties.friendlyName == '{item}'].properties.isScheduledForDeferredDelete | [0]", True),
])

# 4. Rehydrate
self.cmd('backup protection undelete -g {rg} -v {vault} -c {container} -i {item} '
'--backup-management-type AzureStorage --workload-type AzureFileShare', checks=[
self.check("properties.entityFriendlyName", '{item}'),
self.check("properties.operation", "Undelete"),
self.check("properties.status", "Completed"),
self.check("resourceGroup", '{rg}')
])

# 5. Item is rehydrated, no longer soft-deleted
self.cmd('backup item show -g {rg} -v {vault} -c {container} -n {item} '
'--backup-management-type AzureStorage --workload-type AzureFileShare', checks=[
self.check("properties.friendlyName", '{item}'),
self.check("properties.protectionState", "ProtectionStopped"),
self.check("properties.isScheduledForDeferredDelete", None),
])
Loading