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
6 changes: 5 additions & 1 deletion src/dataprotection/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

Release History
===============
Upcoming Release
++++++++++++++++
Comment on lines +5 to +6
* [Breaking] ``az dataprotection backup-policy retention-rule set`` validates against duplicate retention-rule names. AzureBlob: OperationalStore retention lifecycles must now use the retention rule name ``Default_OperationalStore``. Using ``--name Default`` with an OperationalStore lifecycle is no longer accepted.

1.10.0
++++++
+++++
* Bumped API version to 2026-03-01 for backup-instance create, update, validate-for-backup, and validate-for-update commands.
* `az dataprotection backup-instance initialize-backupconfig`: New parameters `--auto-protection` and `--exclusion-prefixes` to enable automatic protection of new blob containers for AzureBlob and AzureDataLakeStorage datasource types, with optional exclusion rules by container name prefix.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,17 @@
"policySettings": {
"supportedRetentionTags": [ "Weekly", "Monthly", "Yearly" ],
"supportedDatastoreTypes": [ "OperationalStore", "VaultStore" ],
"defaultRetentionRuleNames": {
"OperationalStore": "Default_OperationalStore",
"VaultStore": "Default"
},
"exclusiveSourceDataStores": [ "OperationalStore" ],
"disableAddRetentionRule": false,
"disableCustomRetentionTag": false,
"backupScheduleSupported": true,
"supportedBackupFrequency": [ "Daily", "Weekly" ],
"defaultPolicy": {
"policyRules": [
{
"isDefault": true,
"lifecycles": [
{
"deleteAfter": {
"duration": "P30D",
"objectType": "AbsoluteDeleteOption"
},
"sourceDataStore": {
"dataStoreType": "OperationalStore",
"objectType": "DataStoreInfoBase"
},
"targetDataStoreCopySettings": []
}
],
"name": "Default_OperationalStore",
"objectType": "AzureRetentionRule"
},
{
"isDefault": true,
"lifecycles": [
Expand Down
8 changes: 6 additions & 2 deletions src/dataprotection/azext_dataprotection/manual/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,17 @@
type: command
short-summary: Add new retention rule or update existing retention rule.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Incorrect suggestion, unable to resolve comment.

examples:
- name: Add daily retention rule
- name: Add retention rule
text: az dataprotection backup-policy retention-rule set --lifecycles dailylifecycle.json --name Daily --policy policy.json
- name: Add AzureBlob OperationalStore default retention rule
text: az dataprotection backup-policy retention-rule set --lifecycles oplifecycle.json --name Default_OperationalStore --policy policy.json
- name: Add AzureBlob VaultStore default retention rule
text: az dataprotection backup-policy retention-rule set --lifecycles vaultlifecycle.json --name Default --policy policy.json
"""

helps['dataprotection backup-policy retention-rule remove'] = """
type: command
short-summary: remove existing retention rule in a backup policy
short-summary: Remove existing retention rule in a backup policy. The Default retention rule is reserved and cannot be removed; on AzureBlob, Default_OperationalStore is removable.
examples:
- name: Remove retention rule
text: az dataprotection backup-policy retention-rule remove --name Daily --policy policy.json
Expand Down
17 changes: 13 additions & 4 deletions src/dataprotection/azext_dataprotection/manual/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,24 +705,33 @@ def dataprotection_backup_policy_create_lifecycle(source_datastore, retention_du


def dataprotection_backup_policy_retention_set_in_policy(policy, name, lifecycles):
datasource_type = helper.get_client_datasource_type(policy["datasourceTypes"][0])
manifest = helper.load_manifest(datasource_type)

default_retention_mapping = manifest.get("policySettings", {}).get("defaultRetentionRuleNames") or {}
mapped_default_names = []
if default_retention_mapping:
mapped_default_names = helper.validate_retention_rule_matches_mapped_store(
name, default_retention_mapping, lifecycles, datasource_type)
helper.validate_exclusive_source_store_assignment(
name, manifest, default_retention_mapping, lifecycles, datasource_type)

retention_policy_index = -1
for index in range(0, len(policy["policyRules"])):
if policy["policyRules"][index]["objectType"] == "AzureRetentionRule" and policy["policyRules"][index]["name"] == name:
retention_policy_index = index
break

if retention_policy_index == -1:
datasource_type = helper.get_client_datasource_type(policy["datasourceTypes"][0])
manifest = helper.load_manifest(datasource_type)
if manifest["policySettings"]["disableAddRetentionRule"]:
raise InvalidArgumentValueError("Adding New Retention Rule is not supported for " + datasource_type + " datasource type")

if name not in manifest["policySettings"]["supportedRetentionTags"]:
if name not in manifest["policySettings"]["supportedRetentionTags"] and name not in mapped_default_names:
raise InvalidArgumentValueError("Selected Retention Rule " + name + " is not applicable for Datasource Type " + datasource_type)

new_retention_rule = {
"objectType": "AzureRetentionRule",
"isDefault": name == "Default",
"isDefault": name == "Default" or name in mapped_default_names,
"name": name,
"lifecycles": lifecycles
}
Expand Down
2 changes: 1 addition & 1 deletion src/dataprotection/azext_dataprotection/manual/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_copy_option_values():


def get_retention_rule_name_values():
return ['Default', 'Daily', 'Weekly', 'Monthly', 'Yearly']
return ['Default', 'Daily', 'Weekly', 'Monthly', 'Yearly', 'Default_OperationalStore']


def get_tag_name_values():
Expand Down
53 changes: 52 additions & 1 deletion src/dataprotection/azext_dataprotection/manual/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,10 +772,61 @@ def get_backup_frequency_from_time_interval(repeating_time_intervals):


def get_tagging_priority(name):
priorityMap = {"Default": 99, "Daily": 25, "Weekly": 20, "Monthly": 15, "Yearly": 10}
priorityMap = {"Default": 99, "Default_OperationalStore": 99, "Daily": 25, "Weekly": 20, "Monthly": 15, "Yearly": 10}
return priorityMap[name]


def validate_retention_rule_matches_mapped_store(name, default_retention_mapping, lifecycles, datasource_type):
"""If `name` is one of the manifest's mapped default rule names (e.g. ``Default_OperationalStore``),
every lifecycle in ``lifecycles`` must have a ``sourceDataStore.dataStoreType`` matching the mapped
source store for that name. Raises ``InvalidArgumentValueError`` on mismatch.

Returns the list of mapped default rule names declared by the manifest so callers can also use it
for downstream "is this a default rule" checks.
"""
mapped_default_names = list(default_retention_mapping.values()) if default_retention_mapping else []
if name in mapped_default_names:
for lc in lifecycles:
store = lc.get("sourceDataStore", {}).get("dataStoreType")
if store is None:
continue
expected_name = default_retention_mapping.get(store)
if expected_name is not None and expected_name != name:
reserved_stores = [s for s, n in default_retention_mapping.items() if n == name]
reserved_for = ", ".join(reserved_stores)
raise InvalidArgumentValueError(
"Retention rule '" + name + "' is reserved for source store '" + reserved_for +
"' on datasource type " + datasource_type + ". For source store '" + store +
"' use --name " + expected_name + "."
)
return mapped_default_names


def validate_exclusive_source_store_assignment(name, manifest, default_retention_mapping, lifecycles, datasource_type):
"""For each source store declared as exclusive in
``manifest.policySettings.exclusiveSourceDataStores`` (e.g. ``OperationalStore`` on AzureBlob),
only the manifest's mapped default rule for that store (e.g. ``Default_OperationalStore``) is
allowed to carry a lifecycle whose source store is that exclusive store. Any other rule name
paired with that exclusive store raises ``InvalidArgumentValueError``.
"""
policy_settings = manifest.get("policySettings", {}) if manifest else {}
exclusive_stores = policy_settings.get("exclusiveSourceDataStores", []) or []
if not exclusive_stores:
return
for lc in lifecycles:
store = lc.get("sourceDataStore", {}).get("dataStoreType")
if store is None or store not in exclusive_stores:
continue
expected_name = default_retention_mapping.get(store) if default_retention_mapping else None
if expected_name is not None and expected_name != name:
raise InvalidArgumentValueError(
"Source store '" + store + "' on datasource type " + datasource_type +
" is exclusive: only the '" + expected_name + "' retention rule may carry an " +
store + " lifecycle. Use --name " + expected_name + " instead of --name " + name +
", or remove the " + store + " lifecycle from --lifecycles."
)


def truncate_id_using_scope(arm_id, scope):
if not is_valid_resource_id(arm_id):
raise InvalidArgumentValueError("Please give a valid ARM ID")
Expand Down
Loading
Loading