-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[AppConfig] az appconfig kv: Add snapshot reference support
#33278
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
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -308,6 +308,11 @@ def validate_key(namespace): | |||
| raise InvalidArgumentValueError("Key is invalid. Key cannot be a '.' or '..', or contain the '%' character.") | ||||
|
|
||||
|
|
||||
| def validate_snapshot_reference(namespace): | ||||
| if not namespace.snapshot_name or str(namespace.snapshot_name).isspace(): | ||||
| raise RequiredArgumentMissingError("--snapshot-name is required and cannot be empty.") | ||||
|
|
||||
|
||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,7 +50,8 @@ | |||||||||
| SearchFilterOptions, StatusCodes, | ||||||||||
| ImportExportProfiles, CompareFieldsMap, | ||||||||||
| JsonDiff, ImportMode, | ||||||||||
| AIConfigConstants, HttpHeaders) | ||||||||||
| AIConfigConstants, HttpHeaders, | ||||||||||
| SnapshotReferenceConstants) | ||||||||||
| from ._featuremodels import map_keyvalue_to_featureflag | ||||||||||
| from ._json import parse_json_with_comments | ||||||||||
| from ._models import (convert_configurationsetting_to_keyvalue, convert_keyvalue_to_configurationsetting) | ||||||||||
|
|
@@ -648,6 +649,85 @@ def set_keyvault(cmd, | |||||||||
| raise CLIError("Failed to set the keyvault reference '{}' due to a conflicting operation.".format(key)) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def set_snapshot_reference(cmd, | ||||||||||
| key, | ||||||||||
| snapshot_name, | ||||||||||
| name=None, | ||||||||||
| label=None, | ||||||||||
| tags=None, | ||||||||||
| yes=False, | ||||||||||
| connection_string=None, | ||||||||||
| auth_mode="key", | ||||||||||
| endpoint=None): | ||||||||||
| azconfig_client = get_appconfig_data_client(cmd, name, connection_string, auth_mode, endpoint) | ||||||||||
|
|
||||||||||
| snapshot_ref_value = json.dumps({SnapshotReferenceConstants.SNAPSHOT_NAME_KEY: snapshot_name}, ensure_ascii=False) | ||||||||||
| retry_times = 3 | ||||||||||
| retry_interval = 1 | ||||||||||
|
|
||||||||||
| label = label if label and label != SearchFilterOptions.EMPTY_LABEL else None | ||||||||||
|
|
||||||||||
| # generate correlation request id for operations in the same activity | ||||||||||
| correlation_request_id = str(uuid.uuid4()) | ||||||||||
|
|
||||||||||
| for i in range(0, retry_times): | ||||||||||
| retrieved_kv = None | ||||||||||
| set_kv = None | ||||||||||
| new_kv = None | ||||||||||
|
|
||||||||||
| try: | ||||||||||
| retrieved_kv = azconfig_client.get_configuration_setting(key=key, label=label, headers={HttpHeaders.CORRELATION_REQUEST_ID: correlation_request_id}) | ||||||||||
| except ResourceNotFoundError: | ||||||||||
| logger.debug("Key '%s' with label '%s' not found. A new snapshot reference will be created.", key, label) | ||||||||||
| except HttpResponseError as exception: | ||||||||||
| raise CLIErrors.AzureResponseError("Failed to retrieve key-values from config store. " + str(exception)) | ||||||||||
|
|
||||||||||
| if retrieved_kv is None: | ||||||||||
| set_kv = ConfigurationSetting(key=key, | ||||||||||
| label=label, | ||||||||||
| value=snapshot_ref_value, | ||||||||||
| content_type=SnapshotReferenceConstants.SNAPSHOT_REFERENCE_CONTENT_TYPE, | ||||||||||
| tags=tags) | ||||||||||
| else: | ||||||||||
| set_kv = ConfigurationSetting(key=key, | ||||||||||
| label=label, | ||||||||||
| value=snapshot_ref_value, | ||||||||||
| content_type=SnapshotReferenceConstants.SNAPSHOT_REFERENCE_CONTENT_TYPE, | ||||||||||
| tags=retrieved_kv.tags if tags is None else tags, | ||||||||||
| read_only=retrieved_kv.read_only, | ||||||||||
| etag=retrieved_kv.etag) | ||||||||||
|
|
||||||||||
| verification_kv = { | ||||||||||
| "key": set_kv.key, | ||||||||||
| "label": set_kv.label, | ||||||||||
| "content_type": set_kv.content_type, | ||||||||||
| "value": set_kv.value, | ||||||||||
| "tags": set_kv.tags | ||||||||||
| } | ||||||||||
| entry = json.dumps(verification_kv, indent=2, sort_keys=True, ensure_ascii=False) | ||||||||||
| confirmation_message = "Are you sure you want to set the snapshot reference: \n" + entry + "\n" | ||||||||||
| user_confirmation(confirmation_message, yes) | ||||||||||
|
|
||||||||||
| try: | ||||||||||
| if set_kv.etag is None: | ||||||||||
| new_kv = azconfig_client.add_configuration_setting(set_kv, headers={HttpHeaders.CORRELATION_REQUEST_ID: correlation_request_id}) | ||||||||||
| else: | ||||||||||
| new_kv = azconfig_client.set_configuration_setting(set_kv, match_condition=MatchConditions.IfNotModified, headers={HttpHeaders.CORRELATION_REQUEST_ID: correlation_request_id}) | ||||||||||
| return convert_configurationsetting_to_keyvalue(new_kv) | ||||||||||
|
|
||||||||||
| except ResourceReadOnlyError: | ||||||||||
| raise CLIError("Failed to update read only snapshot reference. Unlock the key-value before updating it.") | ||||||||||
| except HttpResponseError as exception: | ||||||||||
| if exception.status_code == StatusCodes.PRECONDITION_FAILED: | ||||||||||
| logger.debug('Retrying setting %s times with exception: concurrent setting operations', i + 1) | ||||||||||
| time.sleep(retry_interval) | ||||||||||
| else: | ||||||||||
| raise CLIErrors.AzureResponseError("Failed to set the snapshot reference due to an exception: " + str(exception)) | ||||||||||
| except Exception as exception: | ||||||||||
| raise CLIError("Failed to set the snapshot reference due to an exception: " + str(exception)) | ||||||||||
| raise CLIError("Failed to set the snapshot reference '{}' due to a conflicting operation.".format(key)) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def delete_key(cmd, | ||||||||||
| key, | ||||||||||
| name=None, | ||||||||||
|
|
@@ -821,6 +901,7 @@ def list_key(cmd, | |||||||||
| top=None, | ||||||||||
| all_=False, | ||||||||||
| resolve_keyvault=False, | ||||||||||
| resolve_snapshot_references=False, | ||||||||||
| auth_mode="key", | ||||||||||
| endpoint=None): | ||||||||||
| if fields and resolve_keyvault: | ||||||||||
|
|
@@ -841,9 +922,46 @@ def list_key(cmd, | |||||||||
| top=top, | ||||||||||
| all_=all_, | ||||||||||
| cli_ctx=cmd.cli_ctx if resolve_keyvault else None) | ||||||||||
|
|
||||||||||
| if resolve_snapshot_references: | ||||||||||
| keyvalues = __resolve_snapshot_references(azconfig_client, keyvalues) | ||||||||||
|
|
||||||||||
|
Comment on lines
907
to
+928
|
||||||||||
| return keyvalues | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def __resolve_snapshot_references(azconfig_client, keyvalues): | ||||||||||
| """Return key-values in the referenced snapshot. The result may contain duplicate keys, | ||||||||||
| which the caller is responsible for handling. | ||||||||||
| """ | ||||||||||
| resolved_keyvalues = [] | ||||||||||
| for keyvalue in keyvalues: | ||||||||||
| content_type = getattr(keyvalue, 'content_type', None) | ||||||||||
| if not (content_type and SnapshotReferenceConstants.SNAPSHOT_REFERENCE_CONTENT_TYPE in content_type): | ||||||||||
|
||||||||||
| if not (content_type and SnapshotReferenceConstants.SNAPSHOT_REFERENCE_CONTENT_TYPE in content_type): | |
| if not ( | |
| isinstance(content_type, str) and | |
| content_type.lower() == SnapshotReferenceConstants.SNAPSHOT_REFERENCE_CONTENT_TYPE.lower()): |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When both --resolve-keyvault and --resolve-snapshot-references are set, snapshot-expanded key-values are fetched via __read_kv_from_config_store(..., snapshot=...) without cli_ctx, so Key Vault references inside the snapshot results will not be resolved. Consider passing cmd.cli_ctx (or a cli_ctx parameter) through the snapshot resolver when resolve_keyvault is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The help text says the key cannot contain the
'%%'character, but the validator rejects a single'%'(seevalidate_key). This looks like an escaping mistake and will surface incorrect CLI help. Update the string to'%'.