From 0f12da7bf3b7b79aae03bad1b22a621fcf1cb46e Mon Sep 17 00:00:00 2001 From: pranav-04 Date: Thu, 5 Mar 2026 15:20:52 +0530 Subject: [PATCH 1/2] Add support for domain-name-scope param for webapp up --- .../cli/command_modules/appservice/_help.py | 3 ++ .../cli/command_modules/appservice/_params.py | 1 + .../cli/command_modules/appservice/custom.py | 6 ++- .../tests/latest/test_webapp_up_commands.py | 48 +++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_help.py b/src/azure-cli/azure/cli/command_modules/appservice/_help.py index 0174f5fa451..e0dad92e98c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2615,6 +2615,9 @@ - name: Create a web app and deploy as a static HTML app. text: > az webapp up --html + - name: Create a web app with a specified domain name scope for unique hostname generation + text: > + az webapp up -n MyUniqueAppName --domain-name-scope TenantReuse """ helps['webapp update'] = """ diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_params.py b/src/azure-cli/azure/cli/command_modules/appservice/_params.py index 7d002d7cafb..bd4dc0b7ea5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -972,6 +972,7 @@ def load_arguments(self, _): arg_type=get_three_state_flag()) c.argument('enable_kudu_warmup', help="If true, kudu will be warmed up before performing deployment for a linux webapp.", arg_type=get_three_state_flag()) + c.argument('auto_generated_domain_name_label_scope', options_list=['--domain-name-scope'], help="Specify the scope of uniqueness for the default hostname during resource creation.", arg_type=get_enum_type(AutoGeneratedDomainNameLabelScope)) with self.argument_context('webapp ssh') as c: c.argument('port', options_list=['--port', '-p'], 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 6373ef7b1c8..386ba088608 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -9082,7 +9082,8 @@ def get_history_triggered_webjob(cmd, resource_group_name, name, webjob_name, sl def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None, sku=None, # pylint: disable=too-many-statements,too-many-branches os_type=None, runtime=None, dryrun=False, logs=False, launch_browser=False, html=False, - app_service_environment=None, track_status=True, enable_kudu_warmup=True, basic_auth=""): + app_service_environment=None, track_status=True, enable_kudu_warmup=True, basic_auth="", + auto_generated_domain_name_label_scope=None): if not name: name = generate_default_app_name(cmd) @@ -9228,7 +9229,8 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None if _create_new_app: logger.warning("Creating webapp '%s' ...", name) create_webapp(cmd, rg_name, name, plan, runtime_version if not html else None, - using_webapp_up=True, language=language) + using_webapp_up=True, language=language, + auto_generated_domain_name_label_scope=auto_generated_domain_name_label_scope) _configure_default_logging(cmd, rg_name, name) else: # for existing app if we might need to update the stack runtime settings helper = _StackRuntimeHelper(cmd, linux=_is_linux, windows=not _is_linux) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py index 2fabac4e711..a1b59ecf051 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py @@ -8,6 +8,7 @@ import unittest import os +import re from pytest import skip import requests from knack.util import CLIError @@ -1369,6 +1370,53 @@ def test_webapp_up_track_runtimestatus_runtimefailed(self, resource_group): import shutil shutil.rmtree(temp_dir) + @live_only() + @AllowLargeResponse(8192) + @ResourceGroupPreparer(random_name_length=24, name_prefix='clitest', location=LINUX_ASP_LOCATION_WEBAPP) + def test_webapp_up_with_domain_name_scope(self, resource_group): + webapp_name = self.create_random_name('up-dnl-app', 24) + zip_file_name = os.path.join(TEST_DIR, 'node-Express-up.zip') + + import zipfile + import tempfile + temp_dir = tempfile.mkdtemp() + zip_ref = zipfile.ZipFile(zip_file_name, 'r') + zip_ref.extractall(temp_dir) + current_working_dir = os.getcwd() + + up_working_dir = os.path.join(temp_dir, 'myExpressApp') + os.chdir(up_working_dir) + + # test dryrun with --domain-name-scope + result = self.cmd('webapp up -n {} --dryrun --domain-name-scope TenantReuse'.format( + webapp_name)).get_output_in_json() + self.assertTrue(result['name'].startswith(webapp_name)) + self.assertIn("node|", result['runtime_version'].lower()) + self.assertEqual(result['os'].lower(), 'linux') + + # test the full E2E operation works + self.cmd('webapp up -n {} -g {} --domain-name-scope TenantReuse'.format( + webapp_name, resource_group)).get_output_in_json() + + # Verify app is created with domain name scope + result = self.cmd('webapp show -n {} -g {}'.format(webapp_name, resource_group), checks=[ + JMESPathCheck('name', webapp_name), + JMESPathCheck('state', 'Running'), + JMESPathCheck('resourceGroup', resource_group), + JMESPathCheck('autoGeneratedDomainNameLabelScope', 'TenantReuse') + ]).get_output_in_json() + + # Verify the default hostname matches the regional pattern with hash + default_hostname = result.get('defaultHostName') + pattern = r'^([a-zA-Z0-9\-]+)-([a-z0-9]{16})\.([a-z]+-\d{2})\.azurewebsites\.net$' + match = re.match(pattern, default_hostname) + self.assertIsNotNone(match, "defaultHostName '{}' does not match expected pattern".format(default_hostname)) + + # cleanup + os.chdir(current_working_dir) + import shutil + shutil.rmtree(temp_dir) + if __name__ == '__main__': unittest.main() From 96edf65a5e184611fe4dfbe6e787c0e5f9564184 Mon Sep 17 00:00:00 2001 From: pranav-04 Date: Fri, 6 Mar 2026 12:36:09 +0530 Subject: [PATCH 2/2] Add plan name and assertions for dnl --- .../appservice/tests/latest/test_webapp_up_commands.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py index a1b59ecf051..9dd590388d9 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_up_commands.py @@ -1374,6 +1374,7 @@ def test_webapp_up_track_runtimestatus_runtimefailed(self, resource_group): @AllowLargeResponse(8192) @ResourceGroupPreparer(random_name_length=24, name_prefix='clitest', location=LINUX_ASP_LOCATION_WEBAPP) def test_webapp_up_with_domain_name_scope(self, resource_group): + plan = self.create_random_name('up-dnlplan', 24) webapp_name = self.create_random_name('up-dnl-app', 24) zip_file_name = os.path.join(TEST_DIR, 'node-Express-up.zip') @@ -1395,8 +1396,8 @@ def test_webapp_up_with_domain_name_scope(self, resource_group): self.assertEqual(result['os'].lower(), 'linux') # test the full E2E operation works - self.cmd('webapp up -n {} -g {} --domain-name-scope TenantReuse'.format( - webapp_name, resource_group)).get_output_in_json() + self.cmd('webapp up -n {} -g {} --plan {} --domain-name-scope TenantReuse'.format( + webapp_name, resource_group, plan)).get_output_in_json() # Verify app is created with domain name scope result = self.cmd('webapp show -n {} -g {}'.format(webapp_name, resource_group), checks=[ @@ -1411,6 +1412,10 @@ def test_webapp_up_with_domain_name_scope(self, resource_group): pattern = r'^([a-zA-Z0-9\-]+)-([a-z0-9]{16})\.([a-z]+-\d{2})\.azurewebsites\.net$' match = re.match(pattern, default_hostname) self.assertIsNotNone(match, "defaultHostName '{}' does not match expected pattern".format(default_hostname)) + app_name, hash_part, region = match.groups() + self.assertTrue(len(hash_part) == 16 and hash_part.islower(), "Hash is not 16 chars or not lowercase.") + self.assertIn('-', region, "Region part does not have '-' separator.") + self.assertEqual(app_name, webapp_name, "App name and defaultHostName app name do not match.") # cleanup os.chdir(current_working_dir)