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
18 changes: 13 additions & 5 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands import LongRunningOperation
from azure.cli.core.commands.progress import IndeterminateProgressBar
from azure.cli.core.util import shell_safe_json_parse, open_page_in_browser, get_json_object, \
from azure.cli.core.util import shell_safe_json_parse, open_page_in_browser, \
ConfiguredDefaultSetter
from azure.cli.core.util import get_az_user_agent, send_raw_request, get_file_json
from azure.cli.core.profiles import ResourceType, get_sdk
Expand Down Expand Up @@ -3975,19 +3975,27 @@ def update_site_configs(cmd, resource_group_name, name, slot=None, number_of_wor
result = {}
for s in generic_configurations:
try:
json_object = get_json_object(s)
json_object = shell_safe_json_parse(s)
for config_name in json_object:
if config_name.lower() == 'ip_security_restrictions':
if config_name.lower() in ('ip_security_restrictions', 'ipsecurityrestrictions'):
updating_ip_security_restrictions = True
result.update(json_object)
Comment on lines +3978 to 3982
except CLIError:
config_name, value = s.split('=', 1)
result[config_name] = value

for config_name, value in result.items():
if config_name.lower() == 'ip_security_restrictions':
if config_name.lower() in ('ip_security_restrictions', 'ipsecurityrestrictions'):
updating_ip_security_restrictions = True
setattr(configs, config_name, value)
# In azure-mgmt-web 11.0.0+, SiteConfig is a MutableMapping with camelCase REST API keys.
# setattr works for known snake_case SDK property aliases (e.g., 'request_tracing_enabled'),
# but silently creates a Python attribute (not a dict entry) for unknown properties.
# Use dict-style assignment for camelCase property names so they are always included
# in the API request body (e.g., 'webJobsEnabled' which is not in the SDK model).
if any(c.isupper() for c in config_name):
configs[config_name] = value
else:
setattr(configs, config_name, value)
Comment on lines +3995 to +3998

if not updating_ip_security_restrictions:
setattr(configs, 'ip_security_restrictions', None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,44 @@ def test_update_site_config(self, is_centauri_functionapp_mock, site_op_mock):
self.assertEqual(site_config.use32_bit_worker_process, None)
self.assertEqual(site_config.java_container, None)

@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp', autospec=True)
def test_update_site_config_generic_configurations_camelcase(self, is_centauri_functionapp_mock, site_op_mock):
"""Verify that camelCase properties in --generic-configurations (e.g. webJobsEnabled)
are correctly passed to the API via the SiteConfig MutableMapping dict interface,
not silently dropped by setattr (GitHub issue #33823)."""
cmd_mock = _get_test_cmd()
SiteConfig = cmd_mock.get_models('SiteConfig')
site_config = SiteConfig()
site_op_mock.return_value = site_config

is_centauri_functionapp_mock.return_value = False
# action: pass webJobsEnabled (camelCase, not a named SDK property) via generic_configurations
update_site_configs(cmd_mock, 'myRG', 'myweb',
generic_configurations=['webJobsEnabled=false'])
# assert: the property must be present in the underlying MutableMapping so that
# the SDK includes it in the API request body
self.assertIn('webJobsEnabled', dict(site_config))
self.assertEqual(site_config['webJobsEnabled'], 'false')

@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.is_centauri_functionapp', autospec=True)
def test_update_site_config_generic_configurations_camelcase_json(self, is_centauri_functionapp_mock, site_op_mock):
"""Verify that camelCase properties in --generic-configurations provided as JSON
(e.g. {"webJobsEnabled": false}) are correctly passed to the API."""
cmd_mock = _get_test_cmd()
SiteConfig = cmd_mock.get_models('SiteConfig')
site_config = SiteConfig()
site_op_mock.return_value = site_config

is_centauri_functionapp_mock.return_value = False
# action: pass webJobsEnabled as a JSON object
update_site_configs(cmd_mock, 'myRG', 'myweb',
generic_configurations=['{"webJobsEnabled": false}'])
# assert: the property must be in the underlying MutableMapping
self.assertIn('webJobsEnabled', dict(site_config))
self.assertEqual(site_config['webJobsEnabled'], False)

@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
def test_list_publish_profiles_on_slots(self, site_op_mock):
site_op_mock.return_value = [b'<publishData><publishProfile publishUrl="ftp://123"/><publishProfile publishUrl="ftp://1234"/></publishData>']
Expand Down