Skip to content
Closed
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
74 changes: 68 additions & 6 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,14 @@
helps['webapp config appsettings set'] = """
type: command
short-summary: Set a web app's settings.
long-summary: Note that setting values are now redacted in the result. Please use the `az webapp config appsettings list` command to view the settings.
long-summary: |
Note that setting values are now redacted in the result. Please use the `az webapp config appsettings list` command to view the settings.

To enable Application Insights monitoring, set the APPLICATIONINSIGHTS_CONNECTION_STRING app setting to the
connection string of your Application Insights resource. You can find the connection string in the Azure Portal
under your Application Insights resource's Overview page. Using the connection string is the recommended approach
over the legacy APPINSIGHTS_INSTRUMENTATIONKEY setting. For more information, see
https://learn.microsoft.com/azure/azure-monitor/app/azure-web-apps
parameters:
- name: --settings
short-summary: Space-separated appsettings in KEY=VALUE format. Use @{file} to load from a file. See https://go.microsoft.com/fwlink/?linkid=2219923 for more information on file format and editing app settings in bulk.
Expand All @@ -1502,6 +1509,12 @@
- name: Set using both key-value pair and a json file with more settings.
text: >
az webapp config appsettings set -g MyResourceGroup -n MyUniqueApp --settings mySetting=value @moreSettings.json
- name: Enable Application Insights monitoring using a connection string (recommended).
text: >
az webapp config appsettings set -g MyResourceGroup -n MyUniqueApp --settings APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://region.in.applicationinsights.azure.com/"
- name: Enable Application Insights monitoring using an instrumentation key (legacy).
text: >
az webapp config appsettings set -g MyResourceGroup -n MyUniqueApp --settings APPINSIGHTS_INSTRUMENTATIONKEY=00000000-0000-0000-0000-000000000000
"""

helps['webapp config backup'] = """
Expand All @@ -1512,10 +1525,22 @@
helps['webapp config backup create'] = """
type: command
short-summary: Create a backup of a web app.
long-summary: |
Create a backup of a web app to an Azure Blob Storage container.
The --container-url must be a SAS URL with write permissions to the target container.

VNet / Private Endpoint note: If the storage account is behind a VNet or private endpoint,
the web app must have VNet integration enabled and the storage account's firewall must allow
access from the web app's outbound VNet. Ensure the SAS token is valid and the app's
outbound traffic can reach the storage account. For more information, see
https://learn.microsoft.com/azure/app-service/web-sites-backup#troubleshooting
examples:
- name: Create a backup of a web app. (autogenerated)
- name: Create a backup of a web app.
text: az webapp config backup create --container-url {container-url} --resource-group MyResourceGroup --webapp-name MyWebapp
crafted: true
- name: Create a backup with a custom name and database.
text: >
az webapp config backup create --container-url {container-url} --resource-group MyResourceGroup --webapp-name MyWebapp --backup-name mybackup --db-name mydb --db-type SqlAzure --db-connection-string "Server=..."
"""

helps['webapp config backup list'] = """
Expand All @@ -1530,6 +1555,21 @@
helps['webapp config backup restore'] = """
type: command
short-summary: Restore a web app from a backup.
long-summary: |
Restore a web app from a previously created backup stored in Azure Blob Storage.

VNet / Private Endpoint note: If the storage account is behind a VNet or private endpoint,
the web app must have VNet integration enabled and the storage account's firewall must allow
access from the web app's outbound VNet. Ensure the SAS token is valid and the app's
outbound traffic can reach the storage account. For more information, see
https://learn.microsoft.com/azure/app-service/web-sites-backup#troubleshooting
examples:
- name: Restore a web app from a backup.
text: >
az webapp config backup restore --container-url {container-url} --resource-group MyResourceGroup --webapp-name MyWebapp --backup-name mybackup
- name: Restore a web app overwriting the existing app.
text: >
az webapp config backup restore --container-url {container-url} --resource-group MyResourceGroup --webapp-name MyWebapp --backup-name mybackup --overwrite
"""

helps['webapp config backup delete'] = """
Expand Down Expand Up @@ -2285,13 +2325,29 @@
helps['webapp log config'] = """
type: command
short-summary: Configure logging for a web app.
examples:
- name: Configure logging for a web app. (autogenerated)
long-summary: |
Configure application-level and web server logging. Web server logs can be stored on the filesystem
or in Azure Blob Storage. When using Azure Blob Storage, provide a SAS URL via --web-server-log-sas-url.
parameters:
- name: --web-server-logging
short-summary: "Configure Web server logging. Use 'filesystem' for local storage, 'azureblobstorage' for Azure Blob Storage, or 'off' to disable."
- name: --web-server-log-sas-url
short-summary: SAS URL to an Azure Blob Storage container for web server log storage. Required when --web-server-logging is set to azureblobstorage.
- name: --web-server-log-retention
short-summary: Number of days to retain web server logs when using Azure Blob Storage (default 3).
examples:
- name: Disable web server logging.
text: az webapp log config --name MyWebapp --resource-group MyResourceGroup --web-server-logging off
crafted: true
- name: Configure logging for a web app. (autogenerated)
- name: Disable Docker container logging.
text: az webapp log config --docker-container-logging off --name MyWebapp --resource-group MyResourceGroup
crafted: true
- name: Enable web server logging to Azure Blob Storage.
text: >
az webapp log config --name MyWebapp --resource-group MyResourceGroup --web-server-logging azureblobstorage --web-server-log-sas-url "https://mystorageaccount.blob.core.windows.net/weblogs?sv=...&sig=..."
- name: Enable application logging to the filesystem at verbose level.
text: >
az webapp log config --name MyWebapp --resource-group MyResourceGroup --application-logging filesystem --level verbose
"""

helps['webapp log download'] = """
Expand Down Expand Up @@ -2431,10 +2487,16 @@
helps['webapp traffic-routing set'] = """
type: command
short-summary: Configure routing traffic to deployment slots.
long-summary: |
Configure the percentage of production traffic routed to deployment slots.
Note: this command updates the site configuration, which may cause a brief app restart on the platform.
This is a known Azure App Service platform behavior when modifying ramp-up rules via the configuration API.
examples:
- name: Configure routing traffic to deployment slots. (autogenerated)
- name: Route 50% of production traffic to the staging slot.
text: az webapp traffic-routing set --distribution staging=50 --name MyWebApp --resource-group MyResourceGroup
crafted: true
- name: Split traffic across multiple slots.
text: az webapp traffic-routing set --distribution staging=30 canary=20 --name MyWebApp --resource-group MyResourceGroup
"""

helps['webapp traffic-routing show'] = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,13 @@ def load_arguments(self, _):
c.argument('level', help='logging level',
arg_type=get_enum_type(['error', 'warning', 'information', 'verbose']))
c.argument('web_server_logging', help='configure Web server logging',
arg_type=get_enum_type(['off', 'filesystem']))
arg_type=get_enum_type(['off', 'filesystem', 'azureblobstorage']))
c.argument('docker_container_logging', help='configure gathering STDOUT and STDERR output from container',
arg_type=get_enum_type(['off', 'filesystem']))
c.argument('web_server_log_sas_url', options_list=['--web-server-log-sas-url'],
help='SAS URL to an Azure Blob Storage container for web server log storage. Required when --web-server-logging is set to azureblobstorage.')
c.argument('web_server_log_retention', type=int, options_list=['--web-server-log-retention'],
help='Number of days to retain web server logs when using Azure Blob Storage. Default: 3.')

with self.argument_context('webapp log tail') as c:
c.argument('provider',
Expand Down
20 changes: 16 additions & 4 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -5464,10 +5464,12 @@ def _get_url(cmd, resource_group_name, name, slot=None):
def config_diagnostics(cmd, resource_group_name, name, level=None,
application_logging=None, web_server_logging=None,
docker_container_logging=None, detailed_error_messages=None,
failed_request_tracing=None, slot=None):
failed_request_tracing=None, slot=None,
web_server_log_sas_url=None, web_server_log_retention=None):
from azure.mgmt.web.models import (FileSystemApplicationLogsConfig, ApplicationLogsConfig,
AzureBlobStorageApplicationLogsConfig, SiteLogsConfig,
HttpLogsConfig, FileSystemHttpLogsConfig,
AzureBlobStorageHttpLogsConfig,
EnabledConfig)
client = web_client_factory(cmd.cli_ctx)
# TODO: ensure we call get_site only once
Expand All @@ -5492,15 +5494,23 @@ def config_diagnostics(cmd, resource_group_name, name, level=None,
http_logs = None
server_logging_option = web_server_logging or docker_container_logging
if server_logging_option:
# TODO: az blob storage log config currently not in use, will be impelemented later.
# Tracked as Issue: #4764 on Github
filesystem_log_config = None
blob_log_config = None
turned_on = server_logging_option != 'off'
if server_logging_option in ['filesystem', 'off']:
# 100 mb max log size, retention lasts 3 days. Yes we hard code it, portal does too
filesystem_log_config = FileSystemHttpLogsConfig(retention_in_mb=100, retention_in_days=3,
enabled=turned_on)
http_logs = HttpLogsConfig(file_system=filesystem_log_config, azure_blob_storage=None)
if server_logging_option == 'azureblobstorage':
if not web_server_log_sas_url:
raise RequiredArgumentMissingError(
'--web-server-log-sas-url is required when --web-server-logging is set to azureblobstorage.')
retention = web_server_log_retention if web_server_log_retention is not None else 3
blob_log_config = AzureBlobStorageHttpLogsConfig(
sas_url=web_server_log_sas_url,
retention_in_days=retention,
enabled=True)
http_logs = HttpLogsConfig(file_system=filesystem_log_config, azure_blob_storage=blob_log_config)
Comment on lines +5509 to +5513
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

web_server_log_sas_url is a credential-bearing SAS URL (contains sig=) and will likely be echoed back in the SiteLogsConfig returned from _generic_site_operation, which means az webapp log config output can leak the SAS token to the console/logs. Consider redacting/masking the SAS URL in the returned object (or returning a sanitized response) while still sending the full value in the request.

Copilot uses AI. Check for mistakes.

detailed_error_messages_logs = (None if detailed_error_messages is None
else EnabledConfig(enabled=detailed_error_messages))
Expand Down Expand Up @@ -5622,6 +5632,8 @@ def set_traffic_routing(cmd, resource_group_name, name, distribution):
site = client.web_apps.get(resource_group_name, name)
if not site:
raise ResourceNotFoundError("'{}' app doesn't exist".format(name))
logger.warning('Traffic routing updates the site configuration, which may cause a brief restart. '
'This is a known platform behavior.')
configs = get_site_configs(cmd, resource_group_name, name)
host_name_split = site.default_host_name.split('.', 1)
host_name_suffix = '.' + host_name_split[1]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from knack.util import CLIError
from azure.cli.core.azclierror import (InvalidArgumentValueError,
MutuallyExclusiveArgumentError,
AzureResponseError)
AzureResponseError,
RequiredArgumentMissingError)
from azure.cli.command_modules.appservice.custom import (set_deployment_user,
update_git_token, add_hostname,
update_site_configs,
Expand All @@ -33,7 +34,9 @@
add_github_actions,
update_app_settings,
update_application_settings_polling,
update_webapp)
update_webapp,
config_diagnostics,
set_traffic_routing)

# pylint: disable=line-too-long
from azure.cli.core.profiles import ResourceType
Expand Down Expand Up @@ -603,6 +606,68 @@ def test_update_app_settings_success_with_slot_settings(self, mock_build, mock_s
mock_client.web_apps.update_slot_configuration_names.assert_called_once()
mock_build.assert_called_once()

@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory')
def test_config_diagnostics_blob_storage_requires_sas_url(self, mock_client_factory):
"""Test that RequiredArgumentMissingError is raised when azureblobstorage is set without SAS URL."""
cmd_mock = _get_test_cmd()

mock_client = mock.MagicMock()
mock_client.web_apps.get.return_value = mock.MagicMock()
mock_client_factory.return_value = mock_client

with self.assertRaisesRegex(RequiredArgumentMissingError,
'--web-server-log-sas-url is required'):
config_diagnostics(cmd_mock, 'test-rg', 'test-app',
web_server_logging='azureblobstorage')

@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation')
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory')
def test_config_diagnostics_blob_storage_with_sas_url(self, mock_client_factory, mock_site_op):
"""Test that blob storage logging is configured when SAS URL is provided."""
cmd_mock = _get_test_cmd()

mock_client = mock.MagicMock()
mock_client.web_apps.get.return_value = mock.MagicMock()
mock_client_factory.return_value = mock_client
mock_site_op.return_value = mock.MagicMock()

sas_url = 'https://mystorageaccount.blob.core.windows.net/logs?sv=2021-06-08&sig=abc'
config_diagnostics(cmd_mock, 'test-rg', 'test-app',
web_server_logging='azureblobstorage',
web_server_log_sas_url=sas_url,
web_server_log_retention=7)

mock_site_op.assert_called_once()
call_args = mock_site_op.call_args
site_log_config = call_args[0][5]
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test inspects the 6th positional argument of _generic_site_operation (call_args[0][5]) to get site_log_config, which is brittle if _generic_site_operation's signature changes. Prefer asserting against mock_site_op.call_args.args[-1] (or kwargs) and/or asserting the expected operation name/slot explicitly to make the test less order-dependent.

Suggested change
site_log_config = call_args[0][5]
site_log_config = call_args.args[-1]

Copilot uses AI. Check for mistakes.
self.assertIsNotNone(site_log_config.http_logs)
self.assertIsNotNone(site_log_config.http_logs.azure_blob_storage)
self.assertEqual(site_log_config.http_logs.azure_blob_storage.sas_url, sas_url)
self.assertEqual(site_log_config.http_logs.azure_blob_storage.retention_in_days, 7)
self.assertTrue(site_log_config.http_logs.azure_blob_storage.enabled)

@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation')
@mock.patch('azure.cli.command_modules.appservice.custom.get_site_configs')
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory')
def test_set_traffic_routing_warns_about_restart(self, mock_client_factory, mock_get_configs, mock_site_op):
"""Test that set_traffic_routing emits a warning about potential restart."""
cmd_mock = _get_test_cmd()

mock_site = mock.MagicMock()
mock_site.default_host_name = 'myapp.azurewebsites.net'
mock_client = mock.MagicMock()
mock_client.web_apps.get.return_value = mock_site
mock_client_factory.return_value = mock_client

mock_configs = mock.MagicMock()
mock_configs.experiments.ramp_up_rules = []
mock_get_configs.return_value = mock_configs

import logging
with self.assertLogs('cli.azure.cli.command_modules.appservice.custom', level=logging.WARNING) as log:
set_traffic_routing(cmd_mock, 'test-rg', 'myapp', ['staging=50'])
self.assertTrue(any('restart' in msg.lower() for msg in log.output))


class TestUpdateWebapp(unittest.TestCase):

Expand Down
Loading