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
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 @@ -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
21 changes: 14 additions & 7 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 @@ -5960,7 +5963,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 +5989,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 = dict(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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
add_github_actions,
update_app_settings,
update_application_settings_polling,
update_webapp)
update_webapp,
add_azure_storage_account,
update_azure_storage_account)

# pylint: disable=line-too-long
from azure.cli.core.profiles import ResourceType
Expand Down Expand Up @@ -468,6 +470,90 @@ def test_create_managed_ssl_cert(self, generic_site_op_mock, client_factory_mock
client.certificates.create_or_update.assert_called_once_with(name=host_name, resource_group_name=rg_name,
certificate_envelope=cert_def)

@mock.patch('azure.cli.command_modules.appservice.custom._verify_hostname_binding', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
def test_create_managed_ssl_cert_with_domain_validation_method(self, generic_site_op_mock, client_factory_mock, verify_binding_mock):
webapp_name = 'someWebAppName'
rg_name = 'someRgName'
farm_id = '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg1/providers/Microsoft.Web/serverfarms/farm1'
host_name = 'child.mycustomdomain.com'

client = mock.MagicMock()
client_factory_mock.return_value = client
cmd_mock = _get_test_cmd()
cli_ctx_mock = mock.MagicMock()
cli_ctx_mock.data = {'subscription_id': 'sub1'}
cmd_mock.cli_ctx = cli_ctx_mock
Site, Certificate = cmd_mock.get_models('Site', 'Certificate')
site = Site(name=webapp_name, location='westeurope')
site.server_farm_id = farm_id
generic_site_op_mock.return_value = site

verify_binding_mock.return_value = True
create_managed_ssl_cert(cmd_mock, rg_name, webapp_name, host_name, None,
domain_validation_method='TXT')

cert_def = Certificate(location='westeurope', canonical_name=host_name,
server_farm_id=farm_id, password='', domain_validation_method='TXT')
client.certificates.create_or_update.assert_called_once_with(name=host_name, resource_group_name=rg_name,
certificate_envelope=cert_def)

@mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
def test_add_azure_storage_account_with_protocol(self, generic_site_op_mock, client_factory_mock, settings_op_mock):
rg_name = 'someRgName'
webapp_name = 'someWebAppName'

cmd_mock = _get_test_cmd()
AzureStorageInfoValue = cmd_mock.get_models('AzureStorageInfoValue')

storage_accounts = mock.MagicMock()
storage_accounts.properties = {}
generic_site_op_mock.return_value = storage_accounts

result_mock = mock.MagicMock()
result_mock.properties = {}
settings_op_mock.return_value = result_mock

add_azure_storage_account(cmd_mock, rg_name, webapp_name, custom_id='myId',
storage_type='AzureFiles', account_name='myAccount',
share_name='myShare', access_key='myKey',
mount_path='/mnt/share', protocol='Nfs')

expected = AzureStorageInfoValue(type='AzureFiles', account_name='myAccount',
share_name='myShare', access_key='myKey',
mount_path='/mnt/share', protocol='Nfs')
self.assertEqual(storage_accounts.properties['myId'].protocol, expected.protocol)

@mock.patch('azure.cli.command_modules.appservice.custom._generic_settings_operation', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom.web_client_factory', autospec=True)
@mock.patch('azure.cli.command_modules.appservice.custom._generic_site_operation', autospec=True)
def test_update_azure_storage_account_with_protocol(self, generic_site_op_mock, client_factory_mock, settings_op_mock):
rg_name = 'someRgName'
webapp_name = 'someWebAppName'

cmd_mock = _get_test_cmd()
AzureStorageInfoValue = cmd_mock.get_models('AzureStorageInfoValue')

existing_config = AzureStorageInfoValue(type='AzureFiles', account_name='myAccount',
share_name='myShare', access_key='myKey',
mount_path='/mnt/share', protocol='Smb')
storage_accounts = mock.MagicMock()
storage_accounts.properties = {'myId': existing_config}
generic_site_op_mock.return_value = storage_accounts

result_mock = mock.MagicMock()
result_mock.properties = {}
settings_op_mock.return_value = result_mock

update_azure_storage_account(cmd_mock, rg_name, webapp_name, custom_id='myId',
protocol='Nfs')

new_config = storage_accounts.properties['myId']
self.assertEqual(new_config.protocol, 'Nfs')


def test_update_app_settings_error_handling_no_parameters(self):
"""Test that MutuallyExclusiveArgumentError is raised when neither settings nor slot_settings are provided."""
Expand Down
Loading