-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[App Service] Fix #32290: az functionapp config appsettings set --slot-settings: Fix the command failure to update existing slot settings
#32291
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
3e1176b
a5e185a
40e0045
f0f7e9d
7dc68fd
ce9d520
5be2f8d
301c400
261a86b
47a6108
f65e501
36ba1ca
71ed94f
ff33936
39f759e
4ed5e9a
5f445af
a916b4b
fdf6f2e
4c27292
cef992e
2446b64
0ae0770
8e63318
6f88a7f
b871eef
988fca4
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 |
|---|---|---|
|
|
@@ -511,60 +511,112 @@ def update_app_settings_functionapp(cmd, resource_group_name, name, settings=Non | |
| return update_app_settings(cmd, resource_group_name, name, settings, slot, slot_settings) | ||
|
|
||
|
|
||
| def _parse_simple_key_value_setting(s, dest): | ||
| """ | ||
| Parse simple key=value settings format. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| s : str | ||
| The setting string to parse. | ||
| dest : dict | ||
| Dictionary to store the parsed setting. | ||
|
|
||
| Returns | ||
| ------- | ||
| bool | ||
| True if parsing succeeded, False otherwise. | ||
| """ | ||
| if ('=' in s and not s.lstrip().startswith(('{"', "[", "{")) and | ||
| not s.startswith('@')): # @ indicates file input | ||
| try: | ||
| setting_name, value = s.split('=', 1) | ||
| dest[setting_name] = value | ||
| return True | ||
| except ValueError: | ||
| pass # Fall back to JSON parsing if split fails | ||
| return False | ||
|
|
||
|
|
||
| def _parse_json_setting(s, result, slot_result, setting_type): | ||
| """ | ||
| Parse JSON format settings. | ||
|
|
||
| Parameters: | ||
| s (str): The input string containing JSON-formatted settings. | ||
| result (dict): A dictionary to store the parsed key-value pairs from the settings. | ||
| slot_result (dict): A dictionary to store slot setting flags for each key. | ||
| setting_type (str): The type of settings being parsed, either "SlotSettings" or "Settings". | ||
|
|
||
| Returns: | ||
| bool: True if parsing was successful, False otherwise. | ||
| """ | ||
| try: | ||
| temp = shell_safe_json_parse(s) | ||
| if isinstance(temp, list): # Accept the output of the "list" command | ||
| for t in temp: | ||
| if 'slotSetting' in t.keys(): | ||
| slot_result[t['name']] = t['slotSetting'] | ||
| elif setting_type == "SlotSettings": | ||
| slot_result[t['name']] = True | ||
| result[t['name']] = t['value'] | ||
| else: | ||
| # Handle JSON objects: setting_type is either "SlotSettings" or "Settings" | ||
| # Different logic needed for slot settings vs regular settings | ||
| if setting_type == "SlotSettings": | ||
| # For slot settings JSON objects, add values to result and mark as slot settings | ||
| result.update(temp) | ||
| for key in temp: | ||
| slot_result[key] = True | ||
| else: | ||
| # For regular settings JSON objects, add values to result only | ||
| result.update(temp) | ||
| return True | ||
| except InvalidArgumentValueError: | ||
| return False | ||
|
|
||
|
|
||
| def _parse_fallback_key_value_setting(s, result): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function does (almost?) the same thing as _parse_simple_key_value_setting in line 514. It would never succeed if _parse_simple_key_value_setting fails. |
||
| """Parse key=value as fallback when JSON parsing fails.""" | ||
| try: | ||
| setting_name, value = s.split('=', 1) | ||
| except ValueError as ex: | ||
| raise InvalidArgumentValueError( | ||
| f"Invalid setting format: '{s}'. Expected 'key=value' format or valid JSON.", | ||
| recommendation="Use 'key=value' format or provide valid JSON like '{\"key\": \"value\"}'." | ||
| ) from ex | ||
| result[setting_name] = value | ||
|
|
||
|
|
||
| def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None, slot_settings=None): | ||
| if not settings and not slot_settings: | ||
| raise MutuallyExclusiveArgumentError('Usage Error: --settings |--slot-settings') | ||
| raise MutuallyExclusiveArgumentError('Please provide either --settings or --slot-settings parameter.') | ||
|
|
||
| settings = settings or [] | ||
| slot_settings = slot_settings or [] | ||
|
|
||
| app_settings = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, | ||
| 'list_application_settings', slot) | ||
| result, slot_result = {}, {} | ||
| # pylint: disable=too-many-nested-blocks | ||
|
|
||
| for src, dest, setting_type in [(settings, result, "Settings"), (slot_settings, slot_result, "SlotSettings")]: | ||
| for s in src: | ||
| # Check if this looks like a simple key=value pair without JSON/dict syntax | ||
| # If so, parse it directly to avoid unnecessary warnings from ast.literal_eval | ||
| if ('=' in s and not s.lstrip().startswith(('{"', "[", "{")) and | ||
| not s.startswith('@')): # @ indicates file input | ||
| try: | ||
| setting_name, value = s.split('=', 1) | ||
| dest[setting_name] = value | ||
| continue | ||
| except ValueError: | ||
| pass # Fall back to JSON parsing if split fails | ||
| # Try simple key=value parsing first | ||
| if _parse_simple_key_value_setting(s, dest): | ||
| continue | ||
|
|
||
| try: | ||
| temp = shell_safe_json_parse(s) | ||
| if isinstance(temp, list): # a bit messy, but we'd like accept the output of the "list" command | ||
| for t in temp: | ||
| if 'slotSetting' in t.keys(): | ||
| slot_result[t['name']] = t['slotSetting'] | ||
| elif setting_type == "SlotSettings": | ||
| slot_result[t['name']] = True | ||
| result[t['name']] = t['value'] | ||
| else: | ||
| dest.update(temp) | ||
| except CLIError: | ||
| setting_name, value = s.split('=', 1) | ||
| dest[setting_name] = value | ||
| result.update(dest) | ||
| # Try JSON parsing | ||
| if _parse_json_setting(s, result, slot_result, setting_type): | ||
| continue | ||
|
|
||
| # Fallback to key=value parsing with error handling | ||
| _parse_fallback_key_value_setting(s, result) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only called if _parse_simple_key_value_setting in line 605 returns false. But then this call will also not succeed, except if there is a problem with slot_result, as this function takes result as a parameter instead of slot_result for a slot setting. |
||
|
|
||
| for setting_name, value in result.items(): | ||
| app_settings.properties[setting_name] = value | ||
| client = web_client_factory(cmd.cli_ctx) | ||
|
|
||
|
|
||
| # TODO: Centauri currently return wrong payload for update appsettings, remove this once backend has the fix. | ||
| if is_centauri_functionapp(cmd, resource_group_name, name): | ||
| update_application_settings_polling(cmd, resource_group_name, name, app_settings, slot, client) | ||
| result = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'list_application_settings', slot) | ||
| else: | ||
| result = _generic_settings_operation(cmd.cli_ctx, resource_group_name, name, | ||
| 'update_application_settings', | ||
| app_settings, slot, client) | ||
|
|
||
| # Process slot configurations before updating application settings to ensure proper configuration order. | ||
| app_settings_slot_cfg_names = [] | ||
| if slot_result: | ||
| slot_cfg_names = client.web_apps.list_slot_configuration_names(resource_group_name, name) | ||
|
|
@@ -578,6 +630,15 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None | |
| app_settings_slot_cfg_names = slot_cfg_names.app_setting_names | ||
| client.web_apps.update_slot_configuration_names(resource_group_name, name, slot_cfg_names) | ||
|
|
||
| # TODO: Centauri currently return wrong payload for update appsettings, remove this once backend has the fix. | ||
| if is_centauri_functionapp(cmd, resource_group_name, name): | ||
| update_application_settings_polling(cmd, resource_group_name, name, app_settings, slot, client) | ||
| result = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'list_application_settings', slot) | ||
| else: | ||
| result = _generic_settings_operation(cmd.cli_ctx, resource_group_name, name, | ||
| 'update_application_settings', | ||
| app_settings, slot, client) | ||
|
|
||
| return _build_app_settings_output(result.properties, app_settings_slot_cfg_names, redact=True) | ||
|
|
||
|
|
||
|
|
@@ -597,7 +658,7 @@ def update_application_settings_polling(cmd, resource_group_name, name, app_sett | |
| time.sleep(5) | ||
| r = send_raw_request(cmd.cli_ctx, method='get', url=poll_url) | ||
| else: | ||
| raise CLIError(ex) | ||
| raise AzureResponseError(f"Failed to update application settings: {str(ex)}") from ex | ||
|
|
||
|
|
||
| def add_azure_storage_account(cmd, resource_group_name, name, custom_id, storage_type, account_name, | ||
|
|
||
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.
Slot settings will be added only to
slot_resulthere. And while items ofslot_settingswill be added toapp_settings_slot_cfg_namesthey will not be added toresultat any point which will cause them to be ignored when passed to_build_app_settings_output(). The value of the slot settings is also not accessed anymore preventing an initial set or update of the settings which was the initial intention of this PR.