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
20 changes: 20 additions & 0 deletions linter_exclusions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,11 @@ functionapp config ssl delete:
certificate_thumbprint:
rule_exclusions:
- option_length_too_long
functionapp config ssl create:
parameters:
domain_validation_method:
rule_exclusions:
- missing_parameter_test_coverage
functionapp config ssl import:
parameters:
key_vault_certificate_name:
Expand Down Expand Up @@ -3922,6 +3927,11 @@ webapp config ssl delete:
certificate_thumbprint:
rule_exclusions:
- option_length_too_long
webapp config ssl create:
parameters:
domain_validation_method:
rule_exclusions:
- missing_parameter_test_coverage
webapp config ssl import:
parameters:
key_vault_certificate_name:
Expand All @@ -3932,6 +3942,16 @@ webapp config ssl unbind:
certificate_thumbprint:
rule_exclusions:
- option_length_too_long
webapp config storage-account add:
parameters:
protocol:
rule_exclusions:
- missing_parameter_test_coverage
webapp config storage-account update:
parameters:
protocol:
rule_exclusions:
- missing_parameter_test_coverage
webapp create:
parameters:
multicontainer_config_file:
Expand Down
17 changes: 17 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,8 @@
examples:
- name: Create a Managed Certificate for cname.mycustomdomain.com.
text: az webapp config ssl create --resource-group MyResourceGroup --name MyWebapp --hostname cname.mycustomdomain.com
- name: Create a Managed Certificate for a child DNS zone using domain validation method.
text: az webapp config ssl create --resource-group MyResourceGroup --name MyWebapp --hostname child.mycustomdomain.com --domain-validation-method TXT
"""

helps['webapp config storage-account'] = """
Expand All @@ -1797,6 +1799,16 @@
--share-name MyShare \\
--access-key MyAccessKey \\
--mount-path /path/to/mount
- name: Add an NFS Azure Files connection with Nfs protocol.
text: >
az webapp config storage-account add -g MyResourceGroup -n MyUniqueApp \\
--custom-id NfsId \\
--storage-type AzureFiles \\
--account-name MyStorageAccount \\
--share-name MyNfsShare \\
--access-key MyAccessKey \\
--mount-path /path/to/mount \\
--protocol Nfs
"""

helps['webapp config storage-account delete'] = """
Expand Down Expand Up @@ -1828,6 +1840,11 @@
az webapp config storage-account update -g MyResourceGroup -n MyUniqueApp \\
--custom-id CustomId \\
--mount-path /path/to/new/mount
- name: Update the protocol for an existing Azure storage account configuration.
text: >
az webapp config storage-account update -g MyResourceGroup -n MyUniqueApp \\
--custom-id CustomId \\
--protocol Nfs
- name: Update an existing Azure storage account configuration on a web app.
text: az webapp config storage-account update --access-key MyAccessKey --account-name MyAccount --custom-id CustomId --mount-path /path/to/new/mount --name MyUniqueApp --resource-group MyResourceGroup --share-name MyShare --storage-type AzureFiles
crafted: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def load_arguments(self, _):
c.ignore('app_instance')
c.argument('resource_group_name', arg_type=resource_group_name_type)
c.argument('location', arg_type=get_location_type(self.cli_ctx))
c.argument('slot', options_list=['--slot', '-s'],
c.argument('slot', options_list=['--slot', '-s'], id_part='child_name_1',
help="the name of the slot. Default to the productions slot if not specified")
c.argument('name', arg_type=webapp_name_arg_type)

Expand Down Expand Up @@ -521,6 +521,9 @@ def load_arguments(self, _):
c.argument('hostname', help='The custom domain name')
c.argument('name', options_list=['--name', '-n'], help='Name of the web app.')
c.argument('resource-group', options_list=['--resource-group', '-g'], help='Name of resource group.')
c.argument('domain_validation_method', options_list=['--domain-validation-method', '--validation-method'],
help='Method used for domain validation. Use this when the certificate needs to validate a '
'child DNS zone, e.g. "TXT" for TXT record validation.')
with self.argument_context(scope + ' config hostname') as c:
c.argument('hostname', completer=get_hostname_completion_list,
help="hostname assigned to the site, such as custom domains", id_part='child_name_1')
Expand Down Expand Up @@ -816,6 +819,9 @@ def load_arguments(self, _):
help='the path which the web app uses to read-write data ex: /share1 or /share2')
c.argument('slot', options_list=['--slot', '-s'],
help="the name of the slot. Default to the productions slot if not specified")
c.argument('protocol', options_list=['--protocol'],
arg_type=get_enum_type(['Smb', 'Nfs']),
help='the protocol used to mount the storage account, e.g. Smb or Nfs')
with self.argument_context('webapp config storage-account add') as c:
c.argument('slot_setting', options_list=['--slot-setting'], help="With slot setting you can decide to make BYOS configuration sticky to a slot, meaning that when that slot is swapped, the storage account stays with that slot.")
with self.argument_context('webapp config storage-account update') as c:
Expand Down
48 changes: 39 additions & 9 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,8 @@ def update_application_settings_polling(cmd, resource_group_name, name, app_sett


def add_azure_storage_account(cmd, resource_group_name, name, custom_id, storage_type, account_name,
share_name, access_key, mount_path=None, slot=None, slot_setting=False):
share_name, access_key, mount_path=None, slot=None, slot_setting=False,
protocol=None):
AzureStorageInfoValue = cmd.get_models('AzureStorageInfoValue')
azure_storage_accounts = _generic_site_operation(cmd.cli_ctx, resource_group_name, name,
'list_azure_storage_accounts', slot)
Expand All @@ -665,7 +666,7 @@ def add_azure_storage_account(cmd, resource_group_name, name, custom_id, storage

azure_storage_accounts.properties[custom_id] = AzureStorageInfoValue(type=storage_type, account_name=account_name,
share_name=share_name, access_key=access_key,
mount_path=mount_path)
mount_path=mount_path, protocol=protocol)
client = web_client_factory(cmd.cli_ctx)

result = _generic_settings_operation(cmd.cli_ctx, resource_group_name, name,
Expand All @@ -684,7 +685,8 @@ def add_azure_storage_account(cmd, resource_group_name, name, custom_id, storage


def update_azure_storage_account(cmd, resource_group_name, name, custom_id, storage_type=None, account_name=None,
share_name=None, access_key=None, mount_path=None, slot=None, slot_setting=False):
share_name=None, access_key=None, mount_path=None, slot=None, slot_setting=False,
protocol=None):
AzureStorageInfoValue = cmd.get_models('AzureStorageInfoValue')

azure_storage_accounts = _generic_site_operation(cmd.cli_ctx, resource_group_name, name,
Expand All @@ -702,7 +704,8 @@ def update_azure_storage_account(cmd, resource_group_name, name, custom_id, stor
account_name=account_name or existing_account_config.account_name,
share_name=share_name or existing_account_config.share_name,
access_key=access_key or existing_account_config.access_key,
mount_path=mount_path or existing_account_config.mount_path
mount_path=mount_path or existing_account_config.mount_path,
protocol=protocol or existing_account_config.protocol
)

azure_storage_accounts.properties[custom_id] = new_account_config
Expand Down Expand Up @@ -3494,6 +3497,18 @@ def _redact_appsettings(settings):
return settings


def _is_json_settings(settings):
"""Check if settings input is in JSON format (e.g. from @file.json)."""
if not settings:
return False
try:
settings_str = ''.join([i.rstrip() for i in settings])
json.loads(settings_str)
return True
except (json.decoder.JSONDecodeError, ValueError):
return False


def _build_app_settings_input(settings, connection_string_type):
if not settings:
return []
Expand Down Expand Up @@ -3527,13 +3542,18 @@ def update_connection_strings(cmd, resource_group_name, name, connection_string_
from azure.mgmt.web.models import ConnStringValueTypePair
if not settings and not slot_settings:
raise ArgumentUsageError('Usage Error: --settings |--slot-settings')
# Detect JSON input before parsing — JSON means replace-all semantics
replace_all = _is_json_settings(settings)
settings = _build_app_settings_input(settings, connection_string_type)
sticky_slot_settings = _build_app_settings_input(slot_settings, connection_string_type)
rm_sticky_slot_settings = set()

conn_strings = _generic_site_operation(cmd.cli_ctx, resource_group_name, name,
'list_connection_strings', slot)

if replace_all:
conn_strings.properties = {}

for name_value_type in settings + sticky_slot_settings:
# split at the first '=', connection string should not have '=' in the name
conn_strings.properties[name_value_type['name']] = ConnStringValueTypePair(value=name_value_type['value'],
Expand All @@ -3549,7 +3569,13 @@ def update_connection_strings(cmd, resource_group_name, name, connection_string_
'update_connection_strings',
conn_strings, slot, client)

if sticky_slot_settings or rm_sticky_slot_settings:
if replace_all:
# Replace-all: slot config names must exactly match new slotSetting=true entries
new_slot_setting_names = set(n['name'] for n in sticky_slot_settings)
slot_cfg_names = client.web_apps.list_slot_configuration_names(resource_group_name, name)
slot_cfg_names.connection_string_names = list(new_slot_setting_names)
client.web_apps.update_slot_configuration_names(resource_group_name, name, slot_cfg_names)
elif sticky_slot_settings or rm_sticky_slot_settings:
new_slot_setting_names = set(n['name'] for n in sticky_slot_settings) # add setting name
slot_cfg_names = client.web_apps.list_slot_configuration_names(resource_group_name, name)
slot_cfg_names.connection_string_names = set(slot_cfg_names.connection_string_names or [])
Expand Down Expand Up @@ -5860,7 +5886,7 @@ def _get_cert(certificate_password, certificate_file):

def list_ssl_certs(cmd, resource_group_name):
client = web_client_factory(cmd.cli_ctx)
return client.certificates.list_by_resource_group(resource_group_name)
return list(client.certificates.list_by_resource_group(resource_group_name))


def show_ssl_cert(cmd, resource_group_name, certificate_name):
Expand Down Expand Up @@ -5960,7 +5986,8 @@ def import_ssl_cert(cmd, resource_group_name, key_vault, key_vault_certificate_n
certificate_envelope=kv_cert_def)


def create_managed_ssl_cert(cmd, resource_group_name, name, hostname, slot=None, certificate_name=None):
def create_managed_ssl_cert(cmd, resource_group_name, name, hostname, slot=None, certificate_name=None,
domain_validation_method=None):
Certificate = cmd.get_models('Certificate')
hostname = hostname.lower()
client = web_client_factory(cmd.cli_ctx)
Expand All @@ -5985,8 +6012,11 @@ def create_managed_ssl_cert(cmd, resource_group_name, name, hostname, slot=None,

server_farm_id = webapp.server_farm_id
location = webapp.location
easy_cert_def = Certificate(location=location, canonical_name=hostname,
server_farm_id=server_farm_id, password='')
cert_kwargs = {"location": location, "canonical_name": hostname,
"server_farm_id": server_farm_id, "password": ''}
if domain_validation_method:
cert_kwargs['domain_validation_method'] = domain_validation_method
easy_cert_def = Certificate(**cert_kwargs)

# TODO: Update manual polling to use LongRunningOperation once backend API & new SDK supports polling
try:
Expand Down
Loading
Loading