Skip to content
Draft
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
7 changes: 6 additions & 1 deletion src/dataprotection/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Release History
===============

1.6.1
+++++
* `az dataprotection backup-instance update`: New parameters: `--backup-configuration` to update AKS datasource parameters.
* Fix in `helpers.py` to correctly prepare/normalize AKS backup-configuration payloads passed via the CLI.

1.6.0
+++++
* Added support for User-Assigned Managed Identities for Backup Instances.
Expand Down Expand Up @@ -184,4 +189,4 @@ Release History

0.1.0
++++++
* Initial release.
* Initial release.
3 changes: 3 additions & 0 deletions src/dataprotection/azext_dataprotection/manual/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ def load_arguments(self, _):
c.argument('vaulted_blob_container_list', type=validate_file_or_dict, options_list=['--vaulted-blob-container-list', '--container-blob-list'],
help="Enter the container list to modify a vaulted blob backup. The output for "
"'az dataprotection backup-instance initialize-backupconfig' needs to be provided as input")
c.argument('backup_configuration', type=validate_file_or_dict,
help="Enter the Backup configuration to modify AKS backup datasource parameters. "
"The output for 'az dataprotection backup-instance initialize-backupconfig' needs to be provided as input.")
c.argument('use_system_assigned_identity', options_list=['--system-assigned', '--use-system-identity', '--use-system-assigned-identity'], arg_type=get_three_state_flag(), help="Use system assigned identity")
c.argument('user_assigned_identity_arm_url', options_list=['--user-assigned', '--user-assigned-identity-arm-url', '--uami'], type=str, help="ARM ID of the User Assigned Managed Identity")

Expand Down
34 changes: 29 additions & 5 deletions src/dataprotection/azext_dataprotection/manual/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def dataprotection_backup_instance_validate_for_update(cmd, resource_group_name,


def dataprotection_backup_instance_update(cmd, resource_group_name, vault_name, backup_instance_name,
vaulted_blob_container_list=None, no_wait=False,
vaulted_blob_container_list=None, backup_configuration=None, no_wait=False,
use_system_assigned_identity=None, user_assigned_identity_arm_url=None):
from azext_dataprotection.aaz.latest.dataprotection.backup_instance import Show as BackupInstanceShow
backup_instance = BackupInstanceShow(cli_ctx=cmd.cli_ctx)(command_args={
Expand All @@ -266,10 +266,34 @@ def dataprotection_backup_instance_update(cmd, resource_group_name, vault_name,
identity_details = helper.get_identity_details(use_system_assigned_identity, user_assigned_identity_arm_url)
backup_instance["properties"]["identityDetails"] = identity_details

# Policy changes - updating the vaulted blob container list for vaulted blob backups
if vaulted_blob_container_list is not None:
backup_instance['properties']['policyInfo']['policyParameters']['backupDatasourceParametersList'] = \
[vaulted_blob_container_list,]
# Policy changes
# - Updating the vaulted blob container list for vaulted blob backups
# - Updating the backup datasource parameters for AKS backups
datasource_type = backup_instance["properties"]["dataSourceInfo"]["datasourceType"]

# If user provided any of the datasource parameter update inputs, handle according to datasource type
if vaulted_blob_container_list is not None or backup_configuration is not None:
if datasource_type == "Microsoft.ContainerService/managedClusters":
if vaulted_blob_container_list is not None:
raise InvalidArgumentValueError('Invalid argument --vaulted-blob-container-list for given datasource type.')
elif backup_configuration is not None:
# Allow passing JSON string or already-parsed object
if isinstance(backup_configuration, str):
import json
try:
backup_configuration = json.loads(backup_configuration)
except Exception:
raise InvalidArgumentValueError("Provided --backup-configuration is not valid JSON")
backup_instance['properties']['policyInfo']['policyParameters']['backupDatasourceParametersList'] = [backup_configuration]

elif datasource_type == "Microsoft.Storage/storageAccounts/blobServices":
if backup_configuration is not None:
raise InvalidArgumentValueError('Invalid argument --backup-configuration for given datasource type.')
elif vaulted_blob_container_list is not None:
backup_instance['properties']['policyInfo']['policyParameters']['backupDatasourceParametersList'] = [vaulted_blob_container_list,]

else:
raise InvalidArgumentValueError('Setting backup datasource parameters is not supported for given DataSourceType')

backup_instance = helper.convert_backup_instance_show_to_input(backup_instance)

Expand Down
10 changes: 10 additions & 0 deletions src/dataprotection/azext_dataprotection/manual/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,16 @@ def convert_backup_instance_show_to_input(backup_instance):
del backup_instance['properties']['protectionStatus']
if 'provisioningState' in backup_instance['properties']:
del backup_instance['properties']['provisioningState']
# Cleaning up resourceProperties if objectType is null to avoid schema validation error
for datasource_property in ['dataSourceInfo', 'dataSourceSetInfo']:
if datasource_property in backup_instance['properties']:
datasource_info = backup_instance['properties'][datasource_property]
if (isinstance(datasource_info, dict) and
'resourceProperties' in datasource_info and
isinstance(datasource_info['resourceProperties'], dict)):
if datasource_info['resourceProperties'].get('objectType') is None:
# Set resourceProperties to None when objectType is null to avoid schema validation error
del backup_instance['properties'][datasource_property]['resourceProperties']
return backup_instance


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from azure.cli.testsdk import ScenarioTest, live_only
from azure.cli.testsdk.scenario_tests import AllowLargeResponse
import time
import copy


def reset_softdelete_base_state(test):
Expand Down Expand Up @@ -273,3 +274,57 @@ def test_dataprotection_backup_instance_softdelete(test):

# Once protection elsewhere is stopped, we can resume protection on the undeleted BI
reset_softdelete_base_state(test)

@AllowLargeResponse()
def test_dataprotection_backup_instance_update_aks_configuration(test):
# Update with AKS backup configuration using simple az CLI commands.
test.kwargs.update({
'location': 'eastus2euap',
'rg': 'clitest-dpp-rg',
'vaultName': 'clitest-bkp-vault-aks-donotdelete',
'policyId': '/subscriptions/38304e13-357e-405e-9e9a-220351dcce8c/resourceGroups/clitest-dpp-rg/providers/Microsoft.DataProtection/backupVaults/clitest-bkp-vault-aks-donotdelete/backupPolicies/akspolicy',
'dataSourceType': 'AzureKubernetesService',
'aksClusterName': 'clitest-cluster1-donotdelete',
'aksClusterId': '/subscriptions/38304e13-357e-405e-9e9a-220351dcce8c/resourceGroups/oss-clitest-rg/providers/Microsoft.ContainerService/managedClusters/clitest-cluster1-donotdelete',
'friendlyName': 'clitest-friendly-aks',
'backupInstanceName': 'clitestsabidonotdelete-clitestsabidonotdelete-887c3538-0bfc-11ee-acd3-002b670b472e'
})

# Fetch original BI backupDatasourceParametersList (if any) to allow resetting later
original_bi = test.cmd('az dataprotection backup-instance show -g "{rg}" --vault-name "{vaultName}" --name "{backupInstanceName}"').get_output_in_json()
original_bi_backup_config_json = original_bi['properties']['policyInfo']['policyParameters'].get('backupDatasourceParametersList')[0]
test.kwargs.update({
'backupConfig': original_bi_backup_config_json
})

# Generate the AKS backup configuration using the dedicated CLI helper
new_backup_config_json = test.cmd('az dataprotection backup-instance initialize-backupconfig --datasource-type AzureKubernetesService').get_output_in_json()

# mutate a visible field to make the change observable
new_backup_config_json['included_namespaces'] = ["nsA", "nsB"]
new_backup_config_json['label_selectors'] = ["app=web"]
new_backup_config_json['excluded_resource_types'] = ["ResourceX"]
new_backup_config_json['include_cluster_scope_resources'] = False
new_backup_config_json['snapshot_volumes'] = False
test.kwargs.update({
'tempBackupConfig': new_backup_config_json
})

# Apply temp configuration
test.cmd('az dataprotection backup-instance update -g "{rg}" --vault-name "{vaultName}" --backup-instance-name "{backupInstanceName}" --backup-configuration "{tempBackupConfig}"', checks=[
test.check('name', "{backupInstanceName}")
])

# Fetch the BI and verify that the backupDatasourceParametersList was updated to reflect the AKS config
test.cmd('az dataprotection backup-instance show -g "{rg}" --vault-name "{vaultName}" --name "{backupInstanceName}"', checks=[
test.check("properties.policyInfo.policyParameters.backupDatasourceParametersList[0].included_namespaces", ['nsA', 'nsB']),
test.check("properties.policyInfo.policyParameters.backupDatasourceParametersList[0].label_selectors", ['app=web']),
test.check("properties.policyInfo.policyParameters.backupDatasourceParametersList[0].excluded_resource_types", ['ResourceX']),
test.check("properties.policyInfo.policyParameters.backupDatasourceParametersList[0].include_cluster_scope_resources", False),
test.check("properties.policyInfo.policyParameters.backupDatasourceParametersList[0].snapshot_volumes", False)
])

# Reset to original configuration
test.cmd('az dataprotection backup-instance update -g "{rg}" --vault-name "{vaultName}" --backup-instance-name "{backupInstanceName}" --backup-configuration "{backupConfig}"', checks=[
test.check('name', "{backupInstanceName}")
])
2 changes: 1 addition & 1 deletion src/dataprotection/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from setuptools import setup, find_packages

# HISTORY.rst entry.
VERSION = '1.6.0'
VERSION = '1.6.1'

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down
Loading