From 3e1176b9b9e5971cf30a33f66f96956b38fa88b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 15:19:15 -0300 Subject: [PATCH 01/24] Improves slot setting handling in app settings update Ensures slot settings passed in object format are properly marked and processed before applying application settings. Moves slot configuration processing earlier to guarantee correct configuration order and maintain expected behavior, especially when mixing regular and slot-specific settings. Relocates workaround for Centauri Function Apps to follow updated logic flow for improved clarity. --- .../cli/command_modules/appservice/custom.py | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 04bfc64a9ab..2f4874d718b 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -545,7 +545,16 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None slot_result[t['name']] = True result[t['name']] = t['value'] else: - dest.update(temp) + # Handle JSON objects - when using --slot-settings with object format, + # treat all keys as slot settings + if setting_type == "SlotSettings": + # For slot settings, we need to add values to result (for app settings) + # AND mark them as slot settings in slot_result + result.update(temp) # Add actual values to result + for key in temp.keys(): + slot_result[key] = True # Mark as slot setting + else: + dest.update(temp) # Regular settings go to dest (which is result) except CLIError: setting_name, value = s.split('=', 1) dest[setting_name] = value @@ -555,16 +564,8 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None 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 + # This ensures that slot settings are properly configured before the values are applied app_settings_slot_cfg_names = [] if slot_result: slot_cfg_names = client.web_apps.list_slot_configuration_names(resource_group_name, name) @@ -578,6 +579,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) From a5e185a5cabf11fae8c94e4a85ce1397645e4a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 15:43:31 -0300 Subject: [PATCH 02/24] Enhances error handling in update_app_settings function and adds unit tests for validation --- .../cli/command_modules/appservice/custom.py | 18 ++- .../latest/test_webapp_commands_thru_mock.py | 143 +++++++++++++++++- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 2f4874d718b..1690258c862 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -513,7 +513,7 @@ def update_app_settings_functionapp(cmd, resource_group_name, name, settings=Non 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 [] @@ -555,10 +555,16 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None slot_result[key] = True # Mark as slot setting else: dest.update(temp) # Regular settings go to dest (which is result) - except CLIError: - setting_name, value = s.split('=', 1) - dest[setting_name] = value - result.update(dest) + except InvalidArgumentValueError: + try: + setting_name, value = s.split('=', 1) + dest[setting_name] = value + result.update(dest) + 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 for setting_name, value in result.items(): app_settings.properties[setting_name] = value @@ -607,7 +613,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, diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index e916920594e..27224b67d8d 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -10,6 +10,9 @@ from azure.mgmt.web import WebSiteManagementClient from knack.util import CLIError +from azure.cli.core.azclierror import (InvalidArgumentValueError, + MutuallyExclusiveArgumentError, + AzureResponseError) from azure.cli.command_modules.appservice.custom import (set_deployment_user, update_git_token, add_hostname, update_site_configs, @@ -27,7 +30,9 @@ list_snapshots, restore_snapshot, create_managed_ssl_cert, - add_github_actions) + add_github_actions, + update_app_settings, + update_application_settings_polling) # pylint: disable=line-too-long from azure.cli.core.profiles import ResourceType @@ -463,6 +468,142 @@ def test_create_managed_ssl_cert(self, generic_site_op_mock, client_factory_mock certificate_envelope=cert_def) + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') + def test_update_app_settings_error_handling_no_parameters(self, mock_build, mock_settings_op, mock_centauri, + mock_client_factory, mock_site_op): + """Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided""" + cmd_mock = _get_test_cmd() + + # Test missing both parameters + with self.assertRaisesRegex(MutuallyExclusiveArgumentError, + "Please provide either --settings or --slot-settings parameter"): + update_app_settings(cmd_mock, 'test-rg', 'test-app') + + # Ensure mocks weren't called since we should fail early + mock_site_op.assert_not_called() + mock_client_factory.assert_not_called() + + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') + @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') + def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse, mock_build, mock_settings_op, + mock_centauri, mock_client_factory, mock_site_op): + """Test that InvalidArgumentValueError is raised for invalid setting formats""" + cmd_mock = _get_test_cmd() + + # Setup mocks + mock_app_settings = mock.MagicMock() + mock_app_settings.properties = {} + mock_site_op.return_value = mock_app_settings + + mock_client = mock.MagicMock() + mock_client_factory.return_value = mock_client + mock_centauri.return_value = False + mock_settings_op.return_value = mock_app_settings + mock_build.return_value = {"success": True} + + # Mock shell_safe_json_parse to raise InvalidArgumentValueError (simulating invalid JSON) + mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") + + # Test invalid format that can't be parsed as JSON or key=value + invalid_setting = "invalid_format_no_equals_no_json" + + with self.assertRaisesRegex(InvalidArgumentValueError, + r"Invalid setting format.*Expected 'key=value' format or valid JSON"): + update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting]) + + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') + def test_update_app_settings_success_key_value_format(self, mock_build, mock_settings_op, mock_centauri, + mock_client_factory, mock_site_op): + """Test successful processing of key=value format settings""" + cmd_mock = _get_test_cmd() + + # Setup mocks + mock_app_settings = mock.MagicMock() + mock_app_settings.properties = {} + mock_site_op.return_value = mock_app_settings + + mock_client = mock.MagicMock() + mock_client_factory.return_value = mock_client + mock_centauri.return_value = False + mock_settings_op.return_value = mock_app_settings + mock_build.return_value = {"KEY1": "value1", "KEY2": "value2"} + + # Test valid key=value format + result = update_app_settings(cmd_mock, 'test-rg', 'test-app', + settings=['KEY1=value1', 'KEY2=value2']) + + # Verify the function completed successfully + self.assertEqual(result["KEY1"], "value1") + self.assertEqual(result["KEY2"], "value2") + mock_build.assert_called_once() + + @mock.patch('azure.cli.command_modules.appservice.custom.send_raw_request') + def test_update_application_settings_polling_error_handling(self, mock_send_request): + """Test that AzureResponseError is raised in polling function when appropriate""" + cmd_mock = _get_test_cmd() + + # Mock an exception that doesn't have the expected structure + class MockException(Exception): + def __init__(self): + self.response = mock.MagicMock() + self.response.status_code = 400 # Not 202 + self.response.headers = {} + + # Mock _generic_settings_operation to raise the exception + with mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') as mock_settings_op: + mock_settings_op.side_effect = MockException() + + with self.assertRaisesRegex(AzureResponseError, "Failed to update application settings"): + update_application_settings_polling(cmd_mock, 'test-rg', 'test-app', + mock.MagicMock(), None, mock.MagicMock()) + + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') + def test_update_app_settings_success_with_slot_settings(self, mock_build, mock_settings_op, mock_centauri, + mock_client_factory, mock_site_op): + """Test successful processing with slot settings""" + cmd_mock = _get_test_cmd() + + # Setup mocks + mock_app_settings = mock.MagicMock() + mock_app_settings.properties = {} + mock_site_op.return_value = mock_app_settings + + mock_client = mock.MagicMock() + mock_slot_config = mock.MagicMock() + mock_slot_config.app_setting_names = [] + mock_client.web_apps.list_slot_configuration_names.return_value = mock_slot_config + mock_client_factory.return_value = mock_client + mock_centauri.return_value = False + mock_settings_op.return_value = mock_app_settings + mock_build.return_value = {"SLOT_KEY": "slot_value"} + + # Test with slot settings + result = update_app_settings(cmd_mock, 'test-rg', 'test-app', + settings=['REGULAR_KEY=regular_value'], + slot_settings=['SLOT_KEY=slot_value']) + + # Verify slot configuration was updated + mock_client.web_apps.list_slot_configuration_names.assert_called_once() + mock_client.web_apps.update_slot_configuration_names.assert_called_once() + mock_build.assert_called_once() + + class FakedResponse: # pylint: disable=too-few-public-methods def __init__(self, status_code): self.status_code = status_code From 40e0045ba1e0be8d4e5248cb93e3336858a6b68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:14:17 -0300 Subject: [PATCH 03/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 1690258c862..36c7a555a0e 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -570,7 +570,7 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None app_settings.properties[setting_name] = value client = web_client_factory(cmd.cli_ctx) - # Process slot configurations BEFORE updating application settings + # Process slot configurations before updating application settings to ensure proper configuration order. # This ensures that slot settings are properly configured before the values are applied app_settings_slot_cfg_names = [] if slot_result: From f0f7e9d1d13714476a1511129f960d8dc3901c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:16:18 -0300 Subject: [PATCH 04/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../azure/cli/command_modules/appservice/custom.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 36c7a555a0e..6866e31f22f 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -545,16 +545,13 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None slot_result[t['name']] = True result[t['name']] = t['value'] else: - # Handle JSON objects - when using --slot-settings with object format, - # treat all keys as slot settings + # For slot settings JSON objects, add values to result and mark as slot settings if setting_type == "SlotSettings": - # For slot settings, we need to add values to result (for app settings) - # AND mark them as slot settings in slot_result - result.update(temp) # Add actual values to result + result.update(temp) for key in temp.keys(): - slot_result[key] = True # Mark as slot setting + slot_result[key] = True else: - dest.update(temp) # Regular settings go to dest (which is result) + dest.update(temp) except InvalidArgumentValueError: try: setting_name, value = s.split('=', 1) From 7dc68fdd2132e95d8c56d33adc35a31e68b7d8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:19:26 -0300 Subject: [PATCH 05/24] Adds error handling test for update_app_settings to validate invalid format without equals sign --- .../latest/test_webapp_commands_thru_mock.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 27224b67d8d..20e3f7405dd 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -519,6 +519,38 @@ def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse r"Invalid setting format.*Expected 'key=value' format or valid JSON"): update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting]) + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') + @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') + @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') + @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') + @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') + @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') + def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_json_parse, mock_build, mock_settings_op, + mock_centauri, mock_client_factory, mock_site_op): + """Test ValueError path when shell_safe_json_parse raises InvalidArgumentValueError and string contains no '='""" + cmd_mock = _get_test_cmd() + + # Setup mocks + mock_app_settings = mock.MagicMock() + mock_app_settings.properties = {} + mock_site_op.return_value = mock_app_settings + + mock_client = mock.MagicMock() + mock_client_factory.return_value = mock_client + mock_centauri.return_value = False + mock_settings_op.return_value = mock_app_settings + mock_build.return_value = {"success": True} + + # Mock shell_safe_json_parse to raise InvalidArgumentValueError + mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") + + # Test invalid format with no equals sign - this should trigger ValueError in split('=', 1) + invalid_setting_no_equals = "invalidformatthatcontainsnoequalsign" + + with self.assertRaisesRegex(InvalidArgumentValueError, + r"Invalid setting format.*Expected 'key=value' format or valid JSON"): + update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting_no_equals]) + @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') From 5be2f8da3d3f28ecc95775d44243ae461554e182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:25:26 -0300 Subject: [PATCH 06/24] Refactor update_app_settings to improve handling of JSON objects and enhance error handling in tests --- .../cli/command_modules/appservice/custom.py | 8 ++-- .../latest/test_webapp_commands_thru_mock.py | 44 +++---------------- 2 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 6866e31f22f..2002f8b4f2e 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -545,13 +545,15 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None slot_result[t['name']] = True result[t['name']] = t['value'] else: - # For slot settings JSON objects, add values to result and mark as slot settings + # Handle JSON objects: different logic 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.keys(): slot_result[key] = True - else: - dest.update(temp) + elif setting_type == "Settings": + # For regular settings JSON objects, add values to result only + result.update(temp) except InvalidArgumentValueError: try: setting_name, value = s.split('=', 1) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 20e3f7405dd..f9586a40600 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -468,47 +468,26 @@ def test_create_managed_ssl_cert(self, generic_site_op_mock, client_factory_mock certificate_envelope=cert_def) - @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') - @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') - @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') - @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') - @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') - def test_update_app_settings_error_handling_no_parameters(self, mock_build, mock_settings_op, mock_centauri, - mock_client_factory, mock_site_op): + def test_update_app_settings_error_handling_no_parameters(self): """Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided""" cmd_mock = _get_test_cmd() - # Test missing both parameters + # Test missing both parameters - should fail early without calling any services with self.assertRaisesRegex(MutuallyExclusiveArgumentError, "Please provide either --settings or --slot-settings parameter"): update_app_settings(cmd_mock, 'test-rg', 'test-app') - - # Ensure mocks weren't called since we should fail early - mock_site_op.assert_not_called() - mock_client_factory.assert_not_called() @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') - @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') - @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') - @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') - @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') - def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse, mock_build, mock_settings_op, - mock_centauri, mock_client_factory, mock_site_op): + def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse, mock_site_op): """Test that InvalidArgumentValueError is raised for invalid setting formats""" cmd_mock = _get_test_cmd() - # Setup mocks + # Setup minimal mocks needed to reach the error handling code mock_app_settings = mock.MagicMock() mock_app_settings.properties = {} mock_site_op.return_value = mock_app_settings - mock_client = mock.MagicMock() - mock_client_factory.return_value = mock_client - mock_centauri.return_value = False - mock_settings_op.return_value = mock_app_settings - mock_build.return_value = {"success": True} - # Mock shell_safe_json_parse to raise InvalidArgumentValueError (simulating invalid JSON) mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") @@ -520,27 +499,16 @@ def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting]) @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') - @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') - @mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp') - @mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') - @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') - def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_json_parse, mock_build, mock_settings_op, - mock_centauri, mock_client_factory, mock_site_op): + def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_json_parse, mock_site_op): """Test ValueError path when shell_safe_json_parse raises InvalidArgumentValueError and string contains no '='""" cmd_mock = _get_test_cmd() - # Setup mocks + # Setup minimal mocks needed to reach the error handling code mock_app_settings = mock.MagicMock() mock_app_settings.properties = {} mock_site_op.return_value = mock_app_settings - mock_client = mock.MagicMock() - mock_client_factory.return_value = mock_client - mock_centauri.return_value = False - mock_settings_op.return_value = mock_app_settings - mock_build.return_value = {"success": True} - # Mock shell_safe_json_parse to raise InvalidArgumentValueError mock_json_parse.side_effect = InvalidArgumentValueError("Invalid JSON format") From 301c400ed5c51aeaaa59f593f7574e00cf814919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:30:59 -0300 Subject: [PATCH 07/24] Fix update_app_settings to correctly update settings dictionary with parsed values --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 2002f8b4f2e..c23de209530 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -557,8 +557,7 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None except InvalidArgumentValueError: try: setting_name, value = s.split('=', 1) - dest[setting_name] = value - result.update(dest) + result[setting_name] = value except ValueError as ex: raise InvalidArgumentValueError( f"Invalid setting format: '{s}'. Expected 'key=value' format or valid JSON.", From 261a86b56906af1edc01c92a3c30d51fec1aaea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:34:21 -0300 Subject: [PATCH 08/24] Refactor update_app_settings to simplify iteration over keys in slot settings --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index c23de209530..6acbedb9410 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -549,7 +549,7 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None 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.keys(): + for key in temp: slot_result[key] = True elif setting_type == "Settings": # For regular settings JSON objects, add values to result only From 47a610833f3d049dd1248a92c35b9e4ca0e26297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:36:25 -0300 Subject: [PATCH 09/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 6acbedb9410..30112619598 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -569,7 +569,6 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None client = web_client_factory(cmd.cli_ctx) # Process slot configurations before updating application settings to ensure proper configuration order. - # This ensures that slot settings are properly configured before the values are applied app_settings_slot_cfg_names = [] if slot_result: slot_cfg_names = client.web_apps.list_slot_configuration_names(resource_group_name, name) From f65e5017d6702127890eb99a70999251a86b0efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:38:23 -0300 Subject: [PATCH 10/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 30112619598..e301f5019f8 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -551,7 +551,7 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None result.update(temp) for key in temp: slot_result[key] = True - elif setting_type == "Settings": + else: # For regular settings JSON objects, add values to result only result.update(temp) except InvalidArgumentValueError: From 36ba1caad4b4428060c77f04ae08c2a55a295e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:44:20 -0300 Subject: [PATCH 11/24] Refactor update_app_settings and its tests to improve clarity in handling JSON objects and expected error messages --- .../cli/command_modules/appservice/custom.py | 3 ++- .../latest/test_webapp_commands_thru_mock.py | 17 ++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index e301f5019f8..1efa47c5e3b 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -545,7 +545,8 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None slot_result[t['name']] = True result[t['name']] = t['value'] else: - # Handle JSON objects: different logic for slot settings vs regular settings + # Handle JSON objects: setting_type is either "SlotSettings" or "Settings" (from line 525 loop) + # 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) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index f9586a40600..6386d9b2455 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -493,9 +493,9 @@ def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse # Test invalid format that can't be parsed as JSON or key=value invalid_setting = "invalid_format_no_equals_no_json" + expected_message = r"Invalid setting format.*Expected 'key=value' format or valid JSON" - with self.assertRaisesRegex(InvalidArgumentValueError, - r"Invalid setting format.*Expected 'key=value' format or valid JSON"): + with self.assertRaisesRegex(InvalidArgumentValueError, expected_message): update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting]) @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @@ -514,9 +514,9 @@ def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_ # Test invalid format with no equals sign - this should trigger ValueError in split('=', 1) invalid_setting_no_equals = "invalidformatthatcontainsnoequalsign" + expected_message = r"Invalid setting format.*Expected 'key=value' format or valid JSON" - with self.assertRaisesRegex(InvalidArgumentValueError, - r"Invalid setting format.*Expected 'key=value' format or valid JSON"): + with self.assertRaisesRegex(InvalidArgumentValueError, expected_message): update_app_settings(cmd_mock, 'test-rg', 'test-app', settings=[invalid_setting_no_equals]) @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @@ -562,12 +562,11 @@ def __init__(self): self.response.headers = {} # Mock _generic_settings_operation to raise the exception - with mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') as mock_settings_op: + with mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation') as mock_settings_op, \ + self.assertRaisesRegex(AzureResponseError, "Failed to update application settings"): mock_settings_op.side_effect = MockException() - - with self.assertRaisesRegex(AzureResponseError, "Failed to update application settings"): - update_application_settings_polling(cmd_mock, 'test-rg', 'test-app', - mock.MagicMock(), None, mock.MagicMock()) + update_application_settings_polling(cmd_mock, 'test-rg', 'test-app', + mock.MagicMock(), None, mock.MagicMock()) @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory') From 71ed94f17d4e41297549d7e5e86b1b02d73eee64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 16:47:23 -0300 Subject: [PATCH 12/24] Fix update_app_settings to correctly handle setting assignment and improve error messaging for invalid formats --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 1efa47c5e3b..7a7bcd607ee 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -558,12 +558,12 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None except InvalidArgumentValueError: try: setting_name, value = s.split('=', 1) - result[setting_name] = value 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 for setting_name, value in result.items(): app_settings.properties[setting_name] = value From ff33936532b6cbee98533ea881d9a3123c52bd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:07:13 -0300 Subject: [PATCH 13/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 6386d9b2455..7c22c2f1e92 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -526,7 +526,7 @@ def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_ @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') def test_update_app_settings_success_key_value_format(self, mock_build, mock_settings_op, mock_centauri, mock_client_factory, mock_site_op): - """Test successful processing of key=value format settings""" + """Test successful processing of key=value format settings.""" cmd_mock = _get_test_cmd() # Setup mocks From 39f759e2a0a46957deaddf487985c694dd2d9465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:07:33 -0300 Subject: [PATCH 14/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 7c22c2f1e92..9981cddd6e6 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -469,7 +469,7 @@ def test_create_managed_ssl_cert(self, generic_site_op_mock, client_factory_mock def test_update_app_settings_error_handling_no_parameters(self): - """Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided""" + """Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided.""" cmd_mock = _get_test_cmd() # Test missing both parameters - should fail early without calling any services From 4ed5e9aba021d6ed144367c53718c1274c89a073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:07:47 -0300 Subject: [PATCH 15/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 9981cddd6e6..c1c58cf9a2a 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -575,7 +575,7 @@ def __init__(self): @mock.patch('azure.cli.command_modules.appservice.custom._build_app_settings_output') def test_update_app_settings_success_with_slot_settings(self, mock_build, mock_settings_op, mock_centauri, mock_client_factory, mock_site_op): - """Test successful processing with slot settings""" + """Test successful processing with slot settings.""" cmd_mock = _get_test_cmd() # Setup mocks From 5f445af8e3256a1507d7b6c586654146d1618a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:08:00 -0300 Subject: [PATCH 16/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index c1c58cf9a2a..6130fc2d2a5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -551,7 +551,7 @@ def test_update_app_settings_success_key_value_format(self, mock_build, mock_set @mock.patch('azure.cli.command_modules.appservice.custom.send_raw_request') def test_update_application_settings_polling_error_handling(self, mock_send_request): - """Test that AzureResponseError is raised in polling function when appropriate""" + """Test that AzureResponseError is raised in polling function when appropriate.""" cmd_mock = _get_test_cmd() # Mock an exception that doesn't have the expected structure From a916b4b9560b404a6c1116cc42cbd339f8476d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:08:11 -0300 Subject: [PATCH 17/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 6130fc2d2a5..03025751092 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -480,7 +480,7 @@ def test_update_app_settings_error_handling_no_parameters(self): @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse, mock_site_op): - """Test that InvalidArgumentValueError is raised for invalid setting formats""" + """Test that InvalidArgumentValueError is raised for invalid setting formats.""" cmd_mock = _get_test_cmd() # Setup minimal mocks needed to reach the error handling code From fdf6f2ed90783ad8822ab6b0b133d590cd8d395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:08:25 -0300 Subject: [PATCH 18/24] Update src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../appservice/tests/latest/test_webapp_commands_thru_mock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py index 03025751092..ee657453c56 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands_thru_mock.py @@ -501,7 +501,7 @@ def test_update_app_settings_error_handling_invalid_format(self, mock_json_parse @mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation') @mock.patch('azure.cli.command_modules.appservice.custom.shell_safe_json_parse') def test_update_app_settings_error_handling_invalid_format_no_equals(self, mock_json_parse, mock_site_op): - """Test ValueError path when shell_safe_json_parse raises InvalidArgumentValueError and string contains no '='""" + """Test ValueError path when shell_safe_json_parse raises InvalidArgumentValueError and string contains no '='.""" cmd_mock = _get_test_cmd() # Setup minimal mocks needed to reach the error handling code From 4c272922ab965e91e862e50156e7f5a644606cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Mon, 20 Oct 2025 18:14:55 -0300 Subject: [PATCH 19/24] Refactor update_app_settings to modularize parsing logic for key=value and JSON settings, improving clarity and error handling --- .../cli/command_modules/appservice/custom.py | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 7a7bcd607ee..bd709878535 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -511,6 +511,58 @@ 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.""" + 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, dest, result, slot_result, setting_type): + """Parse JSON format settings.""" + 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" (from line 525 loop) + # 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): + """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('Please provide either --settings or --slot-settings parameter.') @@ -521,49 +573,19 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None 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: - 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: - # Handle JSON objects: setting_type is either "SlotSettings" or "Settings" (from line 525 loop) - # 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) - except InvalidArgumentValueError: - 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 + # Try simple key=value parsing first + if _parse_simple_key_value_setting(s, dest): + continue + + # Try JSON parsing + if _parse_json_setting(s, dest, result, slot_result, setting_type): + continue + + # Fallback to key=value parsing with error handling + _parse_fallback_key_value_setting(s, result) for setting_name, value in result.items(): app_settings.properties[setting_name] = value From 2446b64229a00fb36c66f0e4d1698fad38496f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:21:11 -0300 Subject: [PATCH 20/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index bd709878535..a4115388f89 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -536,7 +536,7 @@ def _parse_json_setting(s, dest, result, slot_result, setting_type): slot_result[t['name']] = True result[t['name']] = t['value'] else: - # Handle JSON objects: setting_type is either "SlotSettings" or "Settings" (from line 525 loop) + # 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 From 0ae0770bc501b166f9bfb50295dbe7316323caba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:22:56 -0300 Subject: [PATCH 21/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../azure/cli/command_modules/appservice/custom.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index a4115388f89..69d51352eb7 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -525,7 +525,19 @@ def _parse_simple_key_value_setting(s, dest): def _parse_json_setting(s, dest, result, slot_result, setting_type): - """Parse JSON format settings.""" + """ + Parse JSON format settings. + + Parameters: + s (str): The input string containing JSON-formatted settings. + dest (dict): A dictionary for storing parsed settings (may be unused in this function). + 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 From 8e633189250956afacdb5e858be869c9bef31ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= <111895926+jcassanji-southworks@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:23:10 -0300 Subject: [PATCH 22/24] Update src/azure-cli/azure/cli/command_modules/appservice/custom.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cli/command_modules/appservice/custom.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index 69d51352eb7..9d1e1168b20 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -512,7 +512,21 @@ def update_app_settings_functionapp(cmd, resource_group_name, name, settings=Non def _parse_simple_key_value_setting(s, dest): - """Parse simple key=value settings format.""" + """ + 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: From 6f88a7f9fe3ba893600f3a6f8e31941b85eada8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Tue, 21 Oct 2025 13:57:08 -0300 Subject: [PATCH 23/24] Refactor update_app_settings to remove unnecessary whitespace, improving code readability --- .../azure/cli/command_modules/appservice/custom.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index bd709878535..a14168ae36b 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -573,17 +573,17 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None app_settings = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'list_application_settings', slot) result, slot_result = {}, {} - + for src, dest, setting_type in [(settings, result, "Settings"), (slot_settings, slot_result, "SlotSettings")]: for s in src: # Try simple key=value parsing first if _parse_simple_key_value_setting(s, dest): continue - + # Try JSON parsing if _parse_json_setting(s, dest, result, slot_result, setting_type): continue - + # Fallback to key=value parsing with error handling _parse_fallback_key_value_setting(s, result) From 988fca432939b25de78572b499905c295a32c175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Cassanji?= Date: Tue, 21 Oct 2025 14:40:17 -0300 Subject: [PATCH 24/24] Refactor _parse_json_setting to remove unused parameter and simplify function signature --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/custom.py b/src/azure-cli/azure/cli/command_modules/appservice/custom.py index e6e201963d5..09f09208e30 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -538,13 +538,12 @@ def _parse_simple_key_value_setting(s, dest): return False -def _parse_json_setting(s, dest, result, slot_result, setting_type): +def _parse_json_setting(s, result, slot_result, setting_type): """ Parse JSON format settings. Parameters: s (str): The input string containing JSON-formatted settings. - dest (dict): A dictionary for storing parsed settings (may be unused in this function). 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". @@ -607,7 +606,7 @@ def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None continue # Try JSON parsing - if _parse_json_setting(s, dest, result, slot_result, setting_type): + if _parse_json_setting(s, result, slot_result, setting_type): continue # Fallback to key=value parsing with error handling