From 71da1e96cdb49afb3f48c2c396da6610e8c7d16e Mon Sep 17 00:00:00 2001 From: Vandana George Date: Thu, 26 Mar 2026 16:31:12 -0700 Subject: [PATCH 01/10] Improve appservice deploy UX: help text, warnings, and bug fixes for Linux Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../command_modules/appservice/_constants.py | 1 + .../appservice/_create_util.py | 6 +- .../cli/command_modules/appservice/_help.py | 56 +++++++++++++++++- .../cli/command_modules/appservice/_params.py | 22 +++++-- .../cli/command_modules/appservice/custom.py | 57 ++++++++++++++++--- 5 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py index a5af852c9e5..c73611a4bd9 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py @@ -10,6 +10,7 @@ DOTNET_RUNTIME_NAME = "dotnet" NODE_RUNTIME_NAME = "node" PYTHON_RUNTIME_NAME = "python" +JAVA_RUNTIME_NAME = "java" OS_DEFAULT = "Windows" LINUX_OS_NAME = "linux" WINDOWS_OS_NAME = "windows" diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py index 7bc6868266c..053780b884a 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py @@ -14,7 +14,7 @@ from azure.mgmt.web.models import SkuDescription from ._constants import (NETCORE_RUNTIME_NAME, NODE_RUNTIME_NAME, ASPDOTNET_RUNTIME_NAME, STATIC_RUNTIME_NAME, - PYTHON_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, + PYTHON_RUNTIME_NAME, JAVA_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP) from .utils import get_resource_if_exists @@ -191,6 +191,10 @@ def get_lang_from_content(src_path, html=False, is_linux=False): runtime_details_dict['language'] = NODE_RUNTIME_NAME runtime_details_dict['file_loc'] = package_json_file if os.path.isfile(package_json_file) else '' runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT + elif os.path.isfile(os.path.join(src_path, 'pom.xml')): + runtime_details_dict['language'] = JAVA_RUNTIME_NAME + runtime_details_dict['file_loc'] = os.path.join(src_path, 'pom.xml') + runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT elif package_netcore_file: runtime_lang = detect_dotnet_lang(package_netcore_file, is_linux=is_linux) runtime_details_dict['language'] = runtime_lang 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 e0dad92e98c..22532a6ee95 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -1874,7 +1874,17 @@ helps['webapp create'] = """ type: command short-summary: Create a web app. -long-summary: The web app's name must be able to produce a unique FQDN as AppName.azurewebsites.net. +long-summary: | + The web app's name must be able to produce a unique FQDN as AppName.azurewebsites.net. + + This command creates the web app resource but does not deploy code. + + Suggested next steps after creation: + - Deploy your code: + az webapp deploy -g MyResourceGroup -n MyAppName --src-path app.zip --type zip + - For apps loading large models or dependencies at startup, increase the container + start time limit (default 230s, max 1800s): + az webapp config appsettings set -g MyResourceGroup -n MyAppName --settings WEBSITES_CONTAINER_START_TIME_LIMIT=1800 examples: - name: Create a web app with the default configuration. text: > @@ -1910,6 +1920,9 @@ - name: Create a web app with end-to-end encryption enabled and minimum TLS version 1.2 text: > az webapp create -g MyResourceGroup -p MyPlan -n MyUniqueAppName --end-to-end-encryption-enabled true --min-tls-version 1.2 + - name: Create a Linux Python web app with a custom startup command. + text: > + az webapp create -g MyResourceGroup -p MyLinuxPlan -n MyUniqueAppName --runtime "PYTHON:3.12" --startup-file "gunicorn --bind=0.0.0.0 app:app" """ helps['webapp create-remote-connection'] = """ @@ -1985,7 +1998,9 @@ helps['webapp deployment list-publishing-credentials'] = """ type: command -short-summary: Get the details for available web app publishing credentials +short-summary: Get the details for available web app publishing credentials. +long-summary: | + Note: SCM basic authentication must be enabled to use these credentials for deployment. examples: - name: Get the details for available web app publishing credentials (autogenerated) text: az webapp deployment list-publishing-credentials --name MyWebapp --resource-group MyResourceGroup --subscription MySubscription @@ -2070,6 +2085,10 @@ helps['webapp deployment source config-local-git'] = """ type: command short-summary: Get a URL for a git repository endpoint to clone and push to for web app deployment. +long-summary: | + Note: The default deployment branch is 'master'. If your local branch is 'main', + either push with: git push azure main:master, or set the app setting + DEPLOYMENT_BRANCH=main to change the deployment branch. examples: - name: Get an endpoint and add it as a git remote. text: > @@ -2592,6 +2611,10 @@ Each time the command is successfully run, default argument values for resource group, sku, location, plan, and name are saved for the current directory. These defaults are then used for any arguments not provided on subsequent runs of the command in the same directory. Use 'az configure' to manage defaults. Run this command with the --debug parameter to see the API calls and parameters values being used. +long-summary: | + Usage notes: + - If the app already exists, the existing SKU is kept — the --sku flag is ignored for existing apps. + - Progress messages appear as WARNING level — these are informational, not errors. examples: - name: View the details of the app that will be created, without actually running the operation @@ -2603,6 +2626,12 @@ - name: Create a web app with a specified name text: > az webapp up -n MyUniqueAppName + - name: Deploy a Python app to Linux with explicit runtime and plan name. + text: > + az webapp up -n MyApp --runtime "PYTHON:3.12" --plan MyPlan --sku P1v3 + - name: Deploy a .NET app to Linux (must specify --os-type linux). + text: > + az webapp up -n MyDotnetApp --runtime "DOTNETCORE:8.0" --os-type linux --plan MyPlan - name: Create a web app with a specified name and a Java 11 runtime text: > az webapp up -n MyUniqueAppName --runtime "java:11:Java SE:11" @@ -2674,6 +2703,9 @@ helps['webapp webjob'] = """ type: group short-summary: Allows management operations for webjobs on a web app. +long-summary: | + To create WebJobs, use the Azure portal. For more information and other options, + see: https://learn.microsoft.com/azure/app-service/webjobs-create """ helps['webapp webjob continuous'] = """ @@ -3287,7 +3319,27 @@ helps['webapp deploy'] = """ type: command short-summary: Deploys a provided artifact to Azure Web Apps. + long-summary: | + Deploys a zip, war, jar, ear, static file, startup script, or library to an existing Azure Web App. + The web app must already exist — use 'az webapp create' to create one first. + + IMPORTANT: For apps that require dependency installation during deployment (Python, Node.js, + Ruby, PHP), zip deployment does NOT automatically install dependencies. You must set the + app setting SCM_DO_BUILD_DURING_DEPLOYMENT=true before deploying: + + az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true + + Alternatively, use 'az webapp up' which handles resource creation, zipping, and build automatically. + + Supported --type values: zip, war, jar, ear, lib, static, startup. + + Note: Progress messages may appear as WARNING level — these are informational and do not + indicate errors. examples: + - name: Deploy a Python/Node.js app from a zip file (must enable build first). + text: | + az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true + az webapp deploy -g ResourceGroup -n AppName --src-path app.zip --type zip - name: Deploy a war file asynchronously. text: az webapp deploy --resource-group ResourceGroup --name AppName --src-path SourcePath --type war --async true - name: Deploy a static text file to wwwroot/staticfiles/test.txt 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 bd4dc0b7ea5..ef5bc36fadc 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -156,10 +156,11 @@ def load_arguments(self, _): help="Storage mount configurations. Provide key-value pairs for `name= source= type= destination-path= credentials-secret-uri=`.") with self.argument_context('appservice plan update') as c: - c.argument('sku', arg_type=sku_arg_type) + c.argument('sku', arg_type=sku_arg_type, + help='SKU of the app service plan. Use this to scale up (change machine size), e.g. --sku P1v3.') c.argument('elastic_scale', arg_type=get_three_state_flag(), help='Enable or disable automatic scaling. Set to "true" to enable elastic scale for this plan, or "false" to disable elastic scale for this plan. The SKU must be a Premium V2 SKU (P1V2, P2V2, P3V2) or a Premium V3 SKU (P1V3, P2V3, P3V3)') c.argument('max_elastic_worker_count', options_list=['--max-elastic-worker-count', '-m'], type=int, help='Maximum number of instances that the plan can scale out to. The plan must be an elastic scale plan.') - c.argument('number_of_workers', type=int, help='Number of workers to be allocated.') + c.argument('number_of_workers', type=int, help='Number of workers to be allocated. Use this to scale out (add instances), e.g. --number-of-workers 3.') c.ignore('allow_pending_state') c.argument('async_scaling_enabled', arg_type=get_three_state_flag(), help='Enables async scaling for the app service plan. Set to "true" to create an async operation if there are insufficient workers to scale synchronously. The SKU must be Dedicated.') c.argument('default_identity', is_preview=True, @@ -305,7 +306,12 @@ def load_arguments(self, _): validator=validate_site_create, local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.SET], scopes=['webapp', 'cupertino'])) - c.argument('startup_file', help="Linux only. The web's startup file") + c.argument('startup_file', help="Linux only. The web's startup file. " + "Required for FastAPI and other ASGI " + "frameworks (auto-detection is not supported). " + "Example for Flask: \"gunicorn --bind=0.0.0.0 --timeout 600 " + "app:app\". Example for FastAPI: \"gunicorn -k " + "uvicorn.workers.UvicornWorker app:app\".") c.argument('sitecontainers_app', help="If true, a webapp which supports sitecontainers will be created", arg_type=get_three_state_flag()) c.argument('deployment_container_image_name', options_list=['--deployment-container-image-name', '-i'], help='Container image name from container registry, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url'], help='The container registry server url') @@ -951,11 +957,15 @@ def load_arguments(self, _): scopes=['webapp', 'cupertino'])) c.argument('plan', options_list=['--plan', '-p'], completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'), - help="name of the app service plan associated with the webapp", + help="name of the app service plan associated with the webapp. If not specified, a name is auto-generated.", configured_default='appserviceplan') c.argument('sku', arg_type=sku_arg_type) - c.argument('os_type', options_list=['--os-type'], arg_type=get_enum_type(OS_TYPES), help="Set the OS type for the app to be created.") - c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PHP:7.2\"." + c.argument('os_type', options_list=['--os-type'], arg_type=get_enum_type(OS_TYPES), + help="Set the OS type for the app to be created. Defaults to Linux for Python " + "and Node.js runtimes, and to Windows for .NET and ASP.NET runtimes. " + "Use 'linux' explicitly for .NET Linux deployments.") + c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PHP:7.2\". " + "Recommended: always specify explicitly for reliable results. Auto-detection from source files may pick the wrong version. " "Use `az webapp list-runtimes` for available list.") c.argument('dryrun', help="show summary of the create and deploy operation instead of executing it", default=False, action='store_true') 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 386ba088608..eb41597360d 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -147,9 +147,14 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi container_registry_url = parse_docker_image_name(deployment_container_image_name) if container_image_name: - container_image_name = container_image_name if not container_registry_url else "{}/{}".format( - urlparse(container_registry_url).hostname, - container_image_name[1:] if container_image_name.startswith('/') else container_image_name) + if container_registry_url: + registry_host = urlparse(container_registry_url).hostname + # Strip redundant registry host if the image name already includes it + if registry_host and container_image_name.lower().startswith(registry_host.lower() + "/"): + container_image_name = container_image_name[len(registry_host) + 1:] + if container_image_name.startswith('/'): + container_image_name = container_image_name[1:] + container_image_name = "{}/{}".format(registry_host, container_image_name) if deployment_container_image_name: container_image_name = deployment_container_image_name @@ -363,6 +368,8 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi update_site_configs(cmd, resource_group_name, name, acr_identity=acr_identity) _enable_basic_auth(cmd, name, None, resource_group_name, basic_auth.lower()) + if not using_webapp_up: + logger.warning("Webapp '%s' created. Deploy your code with: az webapp deploy or az webapp up", name) return webapp @@ -2816,6 +2823,9 @@ def delete_function_app(cmd, resource_group_name, name, keep_empty_plan=None, sl def delete_webapp(cmd, resource_group_name, name, keep_metrics=None, keep_empty_plan=None, keep_dns_registration=None, slot=None): # pylint: disable=unused-argument client = web_client_factory(cmd.cli_ctx) + if not keep_empty_plan and not slot: + logger.warning("Note: If this is the last app on the plan, the plan will also be deleted. " + "Use --keep-empty-plan to prevent this.") if slot: client.web_apps.delete_slot(resource_group_name, name, slot, delete_metrics=False if keep_metrics else None, @@ -3837,9 +3847,14 @@ def create_webapp_slot(cmd, resource_group_name, webapp, slot, configuration_sou container_registry_url = parse_docker_image_name(deployment_container_image_name) if container_image_name: - container_image_name = container_image_name if not container_registry_url else "{}/{}".format( - urlparse(container_registry_url).hostname, - container_image_name[1:] if container_image_name.startswith('/') else container_image_name) + if container_registry_url: + registry_host = urlparse(container_registry_url).hostname + # Strip redundant registry host if the image name already includes it + if registry_host and container_image_name.lower().startswith(registry_host.lower() + "/"): + container_image_name = container_image_name[len(registry_host) + 1:] + if container_image_name.startswith('/'): + container_image_name = container_image_name[1:] + container_image_name = "{}/{}".format(registry_host, container_image_name) if deployment_container_image_name: container_image_name = deployment_container_image_name @@ -4058,6 +4073,8 @@ def enable_local_git(cmd, resource_group_name, name, slot=None): site_config = get_site_configs(cmd, resource_group_name, name, slot) site_config.scm_type = 'LocalGit' _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'create_or_update_configuration', slot, site_config) + logger.warning("Note: The default deployment branch is 'master'. If your local branch is 'main', " + "either push with: git push azure main:master, or set app setting DEPLOYMENT_BRANCH=main.") return {'url': _get_local_git_url(cmd.cli_ctx, client, resource_group_name, name, slot)} @@ -4298,6 +4315,9 @@ def pre_operations(self): "storage_mounts": storage_mounts, }) + os_type = 'Linux' if is_linux else 'Windows' + logger.warning("App Service Plan '%s' created (%s).", name, os_type) + if no_wait: return poller.result() @@ -9099,6 +9119,8 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None _create_new_app = _site_availability.name_available runtime = _StackRuntimeHelper.remove_delimiters(runtime) os_name = os_type if os_type else detect_os_from_src(src_dir, html, runtime) + if not os_type: + logger.warning("No --os-type specified. Defaulting to '%s'.", os_name) _is_linux = os_name.lower() == LINUX_OS_NAME helper = _StackRuntimeHelper(cmd, linux=_is_linux, windows=not _is_linux) @@ -9118,6 +9140,8 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None _data = get_runtime_version_details(_lang_details.get('file_loc'), language, helper, _is_linux) version_used_create = _data.get('to_create') detected_version = _data.get('detected') + if language: + logger.warning("No --runtime specified. Auto-detected: %s:%s.", language, detected_version) runtime_version = "{}|{}".format(language, version_used_create) if \ version_used_create != "-" else version_used_create @@ -9172,6 +9196,7 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None loc = set_location(cmd, sku, location) rg_name = get_rg_to_use(user, resource_group_name) _create_new_rg = not check_resource_group_exists(cmd, rg_name) + _plan_was_provided = plan is not None plan = get_plan_to_use(cmd=cmd, user=user, loc=loc, @@ -9181,6 +9206,8 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None plan=plan, is_linux=_is_linux, client=client) + if not _plan_was_provided: + logger.warning("No --plan specified. Auto-generated plan: '%s'.", plan) dry_run_str = r""" { "name" : "%s", "appserviceplan" : "%s", @@ -9206,7 +9233,7 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None create_resource_group(cmd, rg_name, loc) logger.warning("Resource group creation complete") # create ASP - logger.warning("Creating AppServicePlan '%s' or Updating if already exists", plan) + logger.warning("Creating AppServicePlan '%s' in '%s' or Updating if already exists", plan, loc) # we will always call the ASP create or update API so that in case of re-deployment, if the SKU or plan setting are # updated we update those try: @@ -9524,6 +9551,22 @@ def perform_onedeploy_webapp(cmd, client = web_client_factory(cmd.cli_ctx) app = client.web_apps.get(resource_group_name, name) params.is_linux_webapp = is_linux_webapp(app) + + # Warn interpreted-language Linux apps that zip deploy won't auto-build + if params.is_linux_webapp and artifact_type in (None, 'zip'): + try: + site_config = get_site_configs(cmd, resource_group_name, name, slot) + linux_fx = getattr(site_config, 'linux_fx_version', '') or '' + stack_prefix = linux_fx.split('|')[0].upper() if '|' in linux_fx else '' + if stack_prefix in ('PYTHON', 'NODE', 'PHP'): + logger.warning( + "Note: 'az webapp deploy' does not install dependencies (pip install, npm install, " + "etc.) for Linux web apps. Ensure your zip includes all dependencies, or set the app " + "setting SCM_DO_BUILD_DURING_DEPLOYMENT=true to enable builds during deployment." + ) + except Exception: # pylint: disable=broad-except + pass + params.is_functionapp = False return _perform_onedeploy_internal(params) From 33aebcedac4e84126835d7a7f5319fbf5d0be13f Mon Sep 17 00:00:00 2001 From: Vandana George Date: Thu, 26 Mar 2026 18:38:05 -0700 Subject: [PATCH 02/10] Revert Java pom.xml detection - needs version parsing too Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure/cli/command_modules/appservice/_constants.py | 1 - .../azure/cli/command_modules/appservice/_create_util.py | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py index c73611a4bd9..a5af852c9e5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_constants.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_constants.py @@ -10,7 +10,6 @@ DOTNET_RUNTIME_NAME = "dotnet" NODE_RUNTIME_NAME = "node" PYTHON_RUNTIME_NAME = "python" -JAVA_RUNTIME_NAME = "java" OS_DEFAULT = "Windows" LINUX_OS_NAME = "linux" WINDOWS_OS_NAME = "windows" diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py index 053780b884a..7bc6868266c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_create_util.py @@ -14,7 +14,7 @@ from azure.mgmt.web.models import SkuDescription from ._constants import (NETCORE_RUNTIME_NAME, NODE_RUNTIME_NAME, ASPDOTNET_RUNTIME_NAME, STATIC_RUNTIME_NAME, - PYTHON_RUNTIME_NAME, JAVA_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, + PYTHON_RUNTIME_NAME, LINUX_SKU_DEFAULT, OS_DEFAULT, DOTNET_RUNTIME_NAME, DOTNET_TARGET_FRAMEWORK_REGEX, GENERATE_RANDOM_APP_NAMES, DOTNET_REFERENCES_DIR_IN_ZIP) from .utils import get_resource_if_exists @@ -191,10 +191,6 @@ def get_lang_from_content(src_path, html=False, is_linux=False): runtime_details_dict['language'] = NODE_RUNTIME_NAME runtime_details_dict['file_loc'] = package_json_file if os.path.isfile(package_json_file) else '' runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT - elif os.path.isfile(os.path.join(src_path, 'pom.xml')): - runtime_details_dict['language'] = JAVA_RUNTIME_NAME - runtime_details_dict['file_loc'] = os.path.join(src_path, 'pom.xml') - runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT elif package_netcore_file: runtime_lang = detect_dotnet_lang(package_netcore_file, is_linux=is_linux) runtime_details_dict['language'] = runtime_lang From 094380279d4acda589e4a2860761bc3d979f7fd7 Mon Sep 17 00:00:00 2001 From: Vandana George Date: Thu, 26 Mar 2026 20:01:48 -0700 Subject: [PATCH 03/10] Improve runtime auto-detection logging with version validation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure/cli/command_modules/appservice/custom.py | 7 ++++++- 1 file changed, 6 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 eb41597360d..67f5363cdc5 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -9141,7 +9141,12 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None version_used_create = _data.get('to_create') detected_version = _data.get('detected') if language: - logger.warning("No --runtime specified. Auto-detected: %s:%s.", language, detected_version) + if not version_used_create or version_used_create == '-': + logger.warning("No --runtime specified. Could not auto-detect a valid %s version. " + "Please specify --runtime explicitly. " + "Use 'az webapp list-runtimes' for available options.", language.upper()) + else: + logger.warning("No --runtime specified. Using %s version: %s.", language, version_used_create) runtime_version = "{}|{}".format(language, version_used_create) if \ version_used_create != "-" else version_used_create From b9f64fe1c91f57f1ee4fd1251a84988477250ede Mon Sep 17 00:00:00 2001 From: Vandana George <118916037+vageorge00@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:30:27 -0700 Subject: [PATCH 04/10] Apply suggestion from @Copilot Good point - no ruby Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/_help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 22532a6ee95..31fa021a152 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -3324,7 +3324,7 @@ The web app must already exist — use 'az webapp create' to create one first. IMPORTANT: For apps that require dependency installation during deployment (Python, Node.js, - Ruby, PHP), zip deployment does NOT automatically install dependencies. You must set the + PHP), zip deployment does NOT automatically install dependencies. You must set the app setting SCM_DO_BUILD_DURING_DEPLOYMENT=true before deploying: az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true From 0b204832795cfbc4e2aac0122cade3e0a59f7f8b Mon Sep 17 00:00:00 2001 From: Vandana George Date: Fri, 27 Mar 2026 19:51:47 -0700 Subject: [PATCH 05/10] Address PR review comments: Hyper-V support, runtime version updates, static site fix, container warning, startup command clarification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli/command_modules/appservice/_help.py | 8 ++--- .../cli/command_modules/appservice/_params.py | 9 ++--- .../cli/command_modules/appservice/custom.py | 34 +++++++++++-------- 3 files changed, 29 insertions(+), 22 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 31fa021a152..8fe4dbe8a15 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -1882,7 +1882,7 @@ Suggested next steps after creation: - Deploy your code: az webapp deploy -g MyResourceGroup -n MyAppName --src-path app.zip --type zip - - For apps loading large models or dependencies at startup, increase the container + - For Linux apps loading large models or dependencies at startup, increase the container start time limit (default 230s, max 1800s): az webapp config appsettings set -g MyResourceGroup -n MyAppName --settings WEBSITES_CONTAINER_START_TIME_LIMIT=1800 examples: @@ -1922,7 +1922,7 @@ az webapp create -g MyResourceGroup -p MyPlan -n MyUniqueAppName --end-to-end-encryption-enabled true --min-tls-version 1.2 - name: Create a Linux Python web app with a custom startup command. text: > - az webapp create -g MyResourceGroup -p MyLinuxPlan -n MyUniqueAppName --runtime "PYTHON:3.12" --startup-file "gunicorn --bind=0.0.0.0 app:app" + az webapp create -g MyResourceGroup -p MyLinuxPlan -n MyUniqueAppName --runtime "PYTHON:3.14" --startup-file "gunicorn --bind=0.0.0.0 app:app" """ helps['webapp create-remote-connection'] = """ @@ -2628,10 +2628,10 @@ az webapp up -n MyUniqueAppName - name: Deploy a Python app to Linux with explicit runtime and plan name. text: > - az webapp up -n MyApp --runtime "PYTHON:3.12" --plan MyPlan --sku P1v3 + az webapp up -n MyApp --runtime "PYTHON:3.14" --plan MyPlan --sku P1v3 - name: Deploy a .NET app to Linux (must specify --os-type linux). text: > - az webapp up -n MyDotnetApp --runtime "DOTNETCORE:8.0" --os-type linux --plan MyPlan + az webapp up -n MyDotnetApp --runtime "DOTNETCORE:10.0" --os-type linux --plan MyPlan - name: Create a web app with a specified name and a Java 11 runtime text: > az webapp up -n MyUniqueAppName --runtime "java:11:Java SE:11" 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 ef5bc36fadc..d3868cdf8e7 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -306,17 +306,18 @@ def load_arguments(self, _): validator=validate_site_create, local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.SET], scopes=['webapp', 'cupertino'])) - c.argument('startup_file', help="Linux only. The web's startup file. " + c.argument('startup_file', help="Linux only. The web's startup command. " "Required for FastAPI and other ASGI " "frameworks (auto-detection is not supported). " "Example for Flask: \"gunicorn --bind=0.0.0.0 --timeout 600 " "app:app\". Example for FastAPI: \"gunicorn -k " - "uvicorn.workers.UvicornWorker app:app\".") + "uvicorn.workers.UvicornWorker app:app\". " + "Example startup script: \"./startup.sh\".") c.argument('sitecontainers_app', help="If true, a webapp which supports sitecontainers will be created", arg_type=get_three_state_flag()) c.argument('deployment_container_image_name', options_list=['--deployment-container-image-name', '-i'], help='Container image name from container registry, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url'], help='The container registry server url') c.argument('container_image_name', options_list=['--container-image-name', '-c'], - help='The container custom image name and optionally the tag name (e.g., `/:`)') + help='The container custom image name and optionally the tag name (e.g., /:). Note: if --container-registry-url is also provided, use : without the registry name.') c.argument('container_registry_user', options_list=['--container-registry-user', '-s', c.deprecate(target='--docker-registry-server-user', redirect='--container-registry-user')], help='The container registry server username') c.argument('container_registry_password', options_list=['--container-registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--container-registry-password')], help='The container registry server password. Required for private registries.') c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES)) @@ -718,7 +719,7 @@ def load_arguments(self, _): help='Container image name, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url', '-r'], help='The container registry server url') c.argument('container_image_name', options_list=['--container-image-name', '-c'], - help='The container custom image name and optionally the tag name (e.g., `/:`)') + help='The container custom image name and optionally the tag name (e.g., /:). Note: if --container-registry-url is also provided, use : without the registry name.') c.argument('container_registry_user', options_list=['--container-registry-user', '-u', c.deprecate(target='--docker-registry-server-user', redirect='--container-registry-user')], help='The container registry server username') c.argument('container_registry_password', options_list=['--container-registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--container-registry-password')], help='The container registry server password') 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 67f5363cdc5..fc296675d2c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -149,12 +149,15 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi if container_image_name: if container_registry_url: registry_host = urlparse(container_registry_url).hostname - # Strip redundant registry host if the image name already includes it + # Warn if image name already includes the registry host if registry_host and container_image_name.lower().startswith(registry_host.lower() + "/"): - container_image_name = container_image_name[len(registry_host) + 1:] - if container_image_name.startswith('/'): - container_image_name = container_image_name[1:] - container_image_name = "{}/{}".format(registry_host, container_image_name) + logger.warning("Note: --container-image-name '%s' appears to include the registry host. " + "The --container-registry-url host is prepended automatically. " + "The resulting image will be: %s/%s", + container_image_name, registry_host, container_image_name) + container_image_name = container_image_name if not container_registry_url else "{}/{}".format( + urlparse(container_registry_url).hostname, + container_image_name[1:] if container_image_name.startswith('/') else container_image_name) if deployment_container_image_name: container_image_name = deployment_container_image_name @@ -3849,12 +3852,15 @@ def create_webapp_slot(cmd, resource_group_name, webapp, slot, configuration_sou if container_image_name: if container_registry_url: registry_host = urlparse(container_registry_url).hostname - # Strip redundant registry host if the image name already includes it + # Warn if image name already includes the registry host if registry_host and container_image_name.lower().startswith(registry_host.lower() + "/"): - container_image_name = container_image_name[len(registry_host) + 1:] - if container_image_name.startswith('/'): - container_image_name = container_image_name[1:] - container_image_name = "{}/{}".format(registry_host, container_image_name) + logger.warning("Note: --container-image-name '%s' appears to include the registry host. " + "The --container-registry-url host is prepended automatically. " + "The resulting image will be: %s/%s", + container_image_name, registry_host, container_image_name) + container_image_name = container_image_name if not container_registry_url else "{}/{}".format( + urlparse(container_registry_url).hostname, + container_image_name[1:] if container_image_name.startswith('/') else container_image_name) if deployment_container_image_name: container_image_name = deployment_container_image_name @@ -4315,8 +4321,8 @@ def pre_operations(self): "storage_mounts": storage_mounts, }) - os_type = 'Linux' if is_linux else 'Windows' - logger.warning("App Service Plan '%s' created (%s).", name, os_type) + os_type = 'Linux' if is_linux else ('Hyper-V' if hyper_v else 'Windows') + logger.warning("Creating App Service Plan '%s' (%s).", name, os_type) if no_wait: return poller.result() @@ -9140,7 +9146,7 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None _data = get_runtime_version_details(_lang_details.get('file_loc'), language, helper, _is_linux) version_used_create = _data.get('to_create') detected_version = _data.get('detected') - if language: + if language and language.lower() != 'static': if not version_used_create or version_used_create == '-': logger.warning("No --runtime specified. Could not auto-detect a valid %s version. " "Please specify --runtime explicitly. " @@ -9566,7 +9572,7 @@ def perform_onedeploy_webapp(cmd, if stack_prefix in ('PYTHON', 'NODE', 'PHP'): logger.warning( "Note: 'az webapp deploy' does not install dependencies (pip install, npm install, " - "etc.) for Linux web apps. Ensure your zip includes all dependencies, or set the app " + "etc.) by default for Linux web apps. Ensure your zip includes all dependencies, or set the app " "setting SCM_DO_BUILD_DURING_DEPLOYMENT=true to enable builds during deployment." ) except Exception: # pylint: disable=broad-except From b74571d49563e129abac2d55c3f15922a67638cd Mon Sep 17 00:00:00 2001 From: Vandana George Date: Mon, 30 Mar 2026 15:04:45 -0700 Subject: [PATCH 06/10] Address PR review round 2: generic SCM_DO_BUILD warning, basic auth fix, startup command clarification, scale up/down/in/out, runtime version updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli/command_modules/appservice/_help.py | 18 ++++++------- .../cli/command_modules/appservice/_params.py | 15 +++++------ .../cli/command_modules/appservice/custom.py | 25 +++++++------------ 3 files changed, 24 insertions(+), 34 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 8fe4dbe8a15..5660d0b7c21 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2000,7 +2000,8 @@ type: command short-summary: Get the details for available web app publishing credentials. long-summary: | - Note: SCM basic authentication must be enabled to use these credentials for deployment. + Note: These credentials require SCM basic authentication to be enabled. + To enable: az webapp update -g -n --basic-auth Enabled examples: - name: Get the details for available web app publishing credentials (autogenerated) text: az webapp deployment list-publishing-credentials --name MyWebapp --resource-group MyResourceGroup --subscription MySubscription @@ -2613,8 +2614,8 @@ Run this command with the --debug parameter to see the API calls and parameters values being used. long-summary: | Usage notes: - - If the app already exists, the existing SKU is kept — the --sku flag is ignored for existing apps. - - Progress messages appear as WARNING level — these are informational, not errors. + - If the app already exists, the existing SKU is kept. The --sku flag is ignored for existing apps. + - Use 'az webapp list-runtimes' to see available runtimes. examples: - name: View the details of the app that will be created, without actually running the operation @@ -3323,20 +3324,15 @@ Deploys a zip, war, jar, ear, static file, startup script, or library to an existing Azure Web App. The web app must already exist — use 'az webapp create' to create one first. - IMPORTANT: For apps that require dependency installation during deployment (Python, Node.js, - PHP), zip deployment does NOT automatically install dependencies. You must set the + IMPORTANT: Zip deployment does NOT automatically run build automation (dependency installation, + compilation, etc.). If your package is not pre-built, you must set the app setting SCM_DO_BUILD_DURING_DEPLOYMENT=true before deploying: az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true - Alternatively, use 'az webapp up' which handles resource creation, zipping, and build automatically. - Supported --type values: zip, war, jar, ear, lib, static, startup. - - Note: Progress messages may appear as WARNING level — these are informational and do not - indicate errors. examples: - - name: Deploy a Python/Node.js app from a zip file (must enable build first). + - name: Enable remote build and deploy an app from a zip file. text: | az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true az webapp deploy -g ResourceGroup -n AppName --src-path app.zip --type zip 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 d3868cdf8e7..84ed0620b9c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -157,10 +157,10 @@ def load_arguments(self, _): with self.argument_context('appservice plan update') as c: c.argument('sku', arg_type=sku_arg_type, - help='SKU of the app service plan. Use this to scale up (change machine size), e.g. --sku P1v3.') + help='SKU of the app service plan. Use this to scale up/down (change machine size), e.g. --sku P1v3.') c.argument('elastic_scale', arg_type=get_three_state_flag(), help='Enable or disable automatic scaling. Set to "true" to enable elastic scale for this plan, or "false" to disable elastic scale for this plan. The SKU must be a Premium V2 SKU (P1V2, P2V2, P3V2) or a Premium V3 SKU (P1V3, P2V3, P3V3)') c.argument('max_elastic_worker_count', options_list=['--max-elastic-worker-count', '-m'], type=int, help='Maximum number of instances that the plan can scale out to. The plan must be an elastic scale plan.') - c.argument('number_of_workers', type=int, help='Number of workers to be allocated. Use this to scale out (add instances), e.g. --number-of-workers 3.') + c.argument('number_of_workers', type=int, help='Number of workers to be allocated. Use this to scale out/in (add or remove instances), e.g. --number-of-workers 3.') c.ignore('allow_pending_state') c.argument('async_scaling_enabled', arg_type=get_three_state_flag(), help='Enables async scaling for the app service plan. Set to "true" to create an async operation if there are insufficient workers to scale synchronously. The SKU must be Dedicated.') c.argument('default_identity', is_preview=True, @@ -306,13 +306,13 @@ def load_arguments(self, _): validator=validate_site_create, local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.SET], scopes=['webapp', 'cupertino'])) - c.argument('startup_file', help="Linux only. The web's startup command. " + c.argument('startup_file', help="Linux only. The web's startup command or script file. " "Required for FastAPI and other ASGI " "frameworks (auto-detection is not supported). " - "Example for Flask: \"gunicorn --bind=0.0.0.0 --timeout 600 " + "Example command: \"gunicorn --bind=0.0.0.0 --timeout 600 " "app:app\". Example for FastAPI: \"gunicorn -k " "uvicorn.workers.UvicornWorker app:app\". " - "Example startup script: \"./startup.sh\".") + "Example script file: \"startup.sh\".") c.argument('sitecontainers_app', help="If true, a webapp which supports sitecontainers will be created", arg_type=get_three_state_flag()) c.argument('deployment_container_image_name', options_list=['--deployment-container-image-name', '-i'], help='Container image name from container registry, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url'], help='The container registry server url') @@ -334,7 +334,7 @@ def load_arguments(self, _): c.argument('acr_use_identity', action='store_true', help="Enable or disable pull image from acr use managed identity") c.argument('acr_identity', help='Accept system or user assigned identity which will be set for acr image pull. ' 'Use \'[system]\' to refer system assigned identity, or a resource id to refer user assigned identity.') - c.argument('basic_auth', help='Enable or disable basic auth for both SCM and FTP Basic Auth Publishing Credentials. Defaults to Enabled if not specified. See https://aka.ms/app-service-basic-auth to learn more.', arg_type=get_enum_type(BASIC_AUTH_TYPES)) + c.argument('basic_auth', help='Enable or disable basic auth for both SCM and FTP Basic Auth Publishing Credentials. Disabled by default for new apps. See https://aka.ms/app-service-basic-auth to learn more.', arg_type=get_enum_type(BASIC_AUTH_TYPES)) 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)) c.argument('end_to_end_encryption_enabled', options_list=['--end-to-end-encryption-enabled', '-e'], help='Enable or disable end-to-end encryption between the Front End and the Workers.', @@ -964,7 +964,8 @@ def load_arguments(self, _): c.argument('os_type', options_list=['--os-type'], arg_type=get_enum_type(OS_TYPES), help="Set the OS type for the app to be created. Defaults to Linux for Python " "and Node.js runtimes, and to Windows for .NET and ASP.NET runtimes. " - "Use 'linux' explicitly for .NET Linux deployments.") + "Use 'linux' explicitly for .NET Linux deployments. " + "Use 'az webapp list-runtimes' to see available runtimes.") c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PHP:7.2\". " "Recommended: always specify explicitly for reliable results. Auto-detection from source files may pick the wrong version. " "Use `az webapp list-runtimes` for available list.") 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 fc296675d2c..f76731e895f 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -372,7 +372,7 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi _enable_basic_auth(cmd, name, None, resource_group_name, basic_auth.lower()) if not using_webapp_up: - logger.warning("Webapp '%s' created. Deploy your code with: az webapp deploy or az webapp up", name) + logger.warning("Webapp '%s' created. Deploy your code with: az webapp deploy", name) return webapp @@ -9207,7 +9207,7 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None loc = set_location(cmd, sku, location) rg_name = get_rg_to_use(user, resource_group_name) _create_new_rg = not check_resource_group_exists(cmd, rg_name) - _plan_was_provided = plan is not None + _plan_not_provided = plan is None plan = get_plan_to_use(cmd=cmd, user=user, loc=loc, @@ -9217,7 +9217,7 @@ def webapp_up(cmd, name=None, resource_group_name=None, plan=None, location=None plan=plan, is_linux=_is_linux, client=client) - if not _plan_was_provided: + if _plan_not_provided: logger.warning("No --plan specified. Auto-generated plan: '%s'.", plan) dry_run_str = r""" { "name" : "%s", @@ -9563,20 +9563,13 @@ def perform_onedeploy_webapp(cmd, app = client.web_apps.get(resource_group_name, name) params.is_linux_webapp = is_linux_webapp(app) - # Warn interpreted-language Linux apps that zip deploy won't auto-build + # Warn that zip deploy won't auto-build on Linux if params.is_linux_webapp and artifact_type in (None, 'zip'): - try: - site_config = get_site_configs(cmd, resource_group_name, name, slot) - linux_fx = getattr(site_config, 'linux_fx_version', '') or '' - stack_prefix = linux_fx.split('|')[0].upper() if '|' in linux_fx else '' - if stack_prefix in ('PYTHON', 'NODE', 'PHP'): - logger.warning( - "Note: 'az webapp deploy' does not install dependencies (pip install, npm install, " - "etc.) by default for Linux web apps. Ensure your zip includes all dependencies, or set the app " - "setting SCM_DO_BUILD_DURING_DEPLOYMENT=true to enable builds during deployment." - ) - except Exception: # pylint: disable=broad-except - pass + logger.warning( + "Note: 'az webapp deploy' does not run build automation (dependency installation, " + "compilation, etc.) by default for Linux web apps. If your package is not pre-built, " + "set the app setting SCM_DO_BUILD_DURING_DEPLOYMENT=true to enable builds during deployment." + ) params.is_functionapp = False return _perform_onedeploy_internal(params) From 0718c9db32eaa52fb6227f98cf5ec089caabda64 Mon Sep 17 00:00:00 2001 From: Vandana George Date: Mon, 30 Mar 2026 15:22:05 -0700 Subject: [PATCH 07/10] Add startup script file example to az webapp create help Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/_help.py | 3 +++ 1 file changed, 3 insertions(+) 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 5660d0b7c21..1f6c39284a3 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -1923,6 +1923,9 @@ - name: Create a Linux Python web app with a custom startup command. text: > az webapp create -g MyResourceGroup -p MyLinuxPlan -n MyUniqueAppName --runtime "PYTHON:3.14" --startup-file "gunicorn --bind=0.0.0.0 app:app" + - name: Create a Linux Python web app with a startup script. + text: > + az webapp create -g MyResourceGroup -p MyLinuxPlan -n MyUniqueAppName --runtime "PYTHON:3.14" --startup-file "startup.sh" """ helps['webapp create-remote-connection'] = """ From 9213bb4942b72f753ab0a9ca409001c4445da8ba Mon Sep 17 00:00:00 2001 From: Vandana George Date: Mon, 30 Mar 2026 16:21:12 -0700 Subject: [PATCH 08/10] Fix linter: wrap placeholders in backticks, use MyResourceGroup style for basic auth Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/_help.py | 2 +- src/azure-cli/azure/cli/command_modules/appservice/_params.py | 4 ++-- 2 files changed, 3 insertions(+), 3 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 1f6c39284a3..0c38b7b2398 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -2004,7 +2004,7 @@ short-summary: Get the details for available web app publishing credentials. long-summary: | Note: These credentials require SCM basic authentication to be enabled. - To enable: az webapp update -g -n --basic-auth Enabled + To enable: az webapp update -g MyResourceGroup -n MyAppName --basic-auth Enabled examples: - name: Get the details for available web app publishing credentials (autogenerated) text: az webapp deployment list-publishing-credentials --name MyWebapp --resource-group MyResourceGroup --subscription MySubscription 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 84ed0620b9c..0af1462db9e 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -317,7 +317,7 @@ def load_arguments(self, _): c.argument('deployment_container_image_name', options_list=['--deployment-container-image-name', '-i'], help='Container image name from container registry, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url'], help='The container registry server url') c.argument('container_image_name', options_list=['--container-image-name', '-c'], - help='The container custom image name and optionally the tag name (e.g., /:). Note: if --container-registry-url is also provided, use : without the registry name.') + help='The container custom image name and optionally the tag name (e.g., `/:`). Note: if --container-registry-url is also provided, use `:` without the registry name.') c.argument('container_registry_user', options_list=['--container-registry-user', '-s', c.deprecate(target='--docker-registry-server-user', redirect='--container-registry-user')], help='The container registry server username') c.argument('container_registry_password', options_list=['--container-registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--container-registry-password')], help='The container registry server password. Required for private registries.') c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES)) @@ -719,7 +719,7 @@ def load_arguments(self, _): help='Container image name, e.g. publisher/image-name:tag', deprecate_info=c.deprecate(target='--deployment-container-image-name')) c.argument('container_registry_url', options_list=['--container-registry-url', '-r'], help='The container registry server url') c.argument('container_image_name', options_list=['--container-image-name', '-c'], - help='The container custom image name and optionally the tag name (e.g., /:). Note: if --container-registry-url is also provided, use : without the registry name.') + help='The container custom image name and optionally the tag name (e.g., `/:`). Note: if --container-registry-url is also provided, use `:` without the registry name.') c.argument('container_registry_user', options_list=['--container-registry-user', '-u', c.deprecate(target='--docker-registry-server-user', redirect='--container-registry-user')], help='The container registry server username') c.argument('container_registry_password', options_list=['--container-registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--container-registry-password')], help='The container registry server password') From 419f586fd34e78d2b9744bed6357523c5c9ca07c Mon Sep 17 00:00:00 2001 From: Vandana George Date: Mon, 30 Mar 2026 17:24:04 -0700 Subject: [PATCH 09/10] Skip deploy suggestion for container/git apps, fix linter HTML tags, add startup script example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/custom.py | 5 ++++- 1 file changed, 4 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 f76731e895f..dc510fe4d4b 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -371,7 +371,10 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi update_site_configs(cmd, resource_group_name, name, acr_identity=acr_identity) _enable_basic_auth(cmd, name, None, resource_group_name, basic_auth.lower()) - if not using_webapp_up: + # Only suggest deployment command when no deployment method is already configured + if not using_webapp_up and not any([container_image_name, deployment_container_image_name, + multicontainer_config_type, sitecontainers_app, + deployment_source_url, deployment_local_git]): logger.warning("Webapp '%s' created. Deploy your code with: az webapp deploy", name) return webapp From 31bac4636338389c141e0e92f729b5ede2f46373 Mon Sep 17 00:00:00 2001 From: Vandana George Date: Wed, 1 Apr 2026 11:48:44 -0700 Subject: [PATCH 10/10] Address review round 3: remove --type zip from example, update PHP:7.2 to PYTHON:3.14, add list-runtimes to create help Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/azure-cli/azure/cli/command_modules/appservice/_help.py | 5 +++-- .../azure/cli/command_modules/appservice/_params.py | 4 ++-- 2 files changed, 5 insertions(+), 4 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 0c38b7b2398..5b7b5b1da03 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_help.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_help.py @@ -1878,10 +1878,11 @@ The web app's name must be able to produce a unique FQDN as AppName.azurewebsites.net. This command creates the web app resource but does not deploy code. + Use 'az webapp list-runtimes' to see available runtimes. Suggested next steps after creation: - Deploy your code: - az webapp deploy -g MyResourceGroup -n MyAppName --src-path app.zip --type zip + az webapp deploy -g MyResourceGroup -n MyAppName --src-path app.zip - For Linux apps loading large models or dependencies at startup, increase the container start time limit (default 230s, max 1800s): az webapp config appsettings set -g MyResourceGroup -n MyAppName --settings WEBSITES_CONTAINER_START_TIME_LIMIT=1800 @@ -3338,7 +3339,7 @@ - name: Enable remote build and deploy an app from a zip file. text: | az webapp config appsettings set -g ResourceGroup -n AppName --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true - az webapp deploy -g ResourceGroup -n AppName --src-path app.zip --type zip + az webapp deploy -g ResourceGroup -n AppName --src-path app.zip - name: Deploy a war file asynchronously. text: az webapp deploy --resource-group ResourceGroup --name AppName --src-path SourcePath --type war --async true - name: Deploy a static text file to wwwroot/staticfiles/test.txt 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 0af1462db9e..1544d99df6c 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -322,7 +322,7 @@ def load_arguments(self, _): c.argument('container_registry_password', options_list=['--container-registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--container-registry-password')], help='The container registry server password. Required for private registries.') c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES)) c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="Linux only. Config file for multicontainer apps. (local or remote)") - c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PHP:7.2\"." + c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PYTHON:3.14\"." "Use `az webapp list-runtimes` for available list") # TODO ADD completer c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan', completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'), @@ -966,7 +966,7 @@ def load_arguments(self, _): "and Node.js runtimes, and to Windows for .NET and ASP.NET runtimes. " "Use 'linux' explicitly for .NET Linux deployments. " "Use 'az webapp list-runtimes' to see available runtimes.") - c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PHP:7.2\". " + c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework:Version, e.g. \"PYTHON:3.14\". " "Recommended: always specify explicitly for reliable results. Auto-detection from source files may pick the wrong version. " "Use `az webapp list-runtimes` for available list.") c.argument('dryrun', help="show summary of the create and deploy operation instead of executing it",