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 2409a138564..5892b9a8b3a 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_params.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_params.py @@ -46,10 +46,9 @@ def load_arguments(self, _): # PARAMETER REGISTRATION name_arg_type = CLIArgumentType(options_list=['--name', '-n'], metavar='NAME') sku_arg_type = CLIArgumentType( - help='The pricing tiers, e.g., F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1V2(Premium V2 Small), P1V3(Premium V3 Small), P2V3(Premium V3 Medium), P3V3(Premium V3 Large), PC2 (Premium Container Small), PC3 (Premium Container Medium), PC4 (Premium Container Large), I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large), I1v2 (Isolated V2 Small), I2v2 (Isolated V2 Medium), I3v2 (Isolated V2 Large), WS1 (Logic Apps Workflow Standard 1), WS2 (Logic Apps Workflow Standard 2), WS3 (Logic Apps Workflow Standard 3)', + help='The pricing tiers, e.g., F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1V2(Premium V2 Small), P1V3(Premium V3 Small), P2V3(Premium V3 Medium), P3V3(Premium V3 Large), I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large), I1v2 (Isolated V2 Small), I2v2 (Isolated V2 Medium), I3v2 (Isolated V2 Large), WS1 (Logic Apps Workflow Standard 1), WS2 (Logic Apps Workflow Standard 2), WS3 (Logic Apps Workflow Standard 3)', arg_type=get_enum_type( - ['F1', 'FREE', 'D1', 'SHARED', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1V2', 'P2V2', 'P3V2', 'P1V3', 'P2V3', 'P3V3', 'PC2', 'PC3', - 'PC4', 'I1', 'I2', 'I3', 'I1v2', 'I2v2', 'I3v2', 'WS1', 'WS2', 'WS3'])) + ['F1', 'FREE', 'D1', 'SHARED', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1V2', 'P2V2', 'P3V2', 'P1V3', 'P2V3', 'P3V3', 'I1', 'I2', 'I3', 'I1v2', 'I2v2', 'I3v2', 'WS1', 'WS2', 'WS3'])) webapp_name_arg_type = CLIArgumentType(configured_default='web', options_list=['--name', '-n'], metavar='NAME', completer=get_resource_name_completion_list('Microsoft.Web/sites'), id_part='name', @@ -786,9 +785,7 @@ def load_arguments(self, _): c.argument('zone_redundant', options_list=['--zone-redundant', '-z'], help='Enable zone redundancy for high availability. Cannot be changed after plan creation. Minimum instance count is 3.') c.argument('sku', required=True, help='The SKU of the app service plan. e.g., F1(Free), D1(Shared), B1(Basic Small), ' 'B2(Basic Medium), B3(Basic Large), S1(Standard Small), ' - 'P1V2(Premium V2 Small), PC2 (Premium Container Small), PC3 ' - '(Premium Container Medium), PC4 (Premium Container Large), I1 ' - '(Isolated Small), I2 (Isolated Medium), I3 (Isolated Large), K1 ' + 'P1V2(Premium V2 Small), I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large), K1 ' '(Kubernetes).') with self.argument_context('functionapp plan update') as c: diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_validators.py b/src/azure-cli/azure/cli/command_modules/appservice/_validators.py index e52c2e937e0..4e34fc25581 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_validators.py @@ -370,7 +370,7 @@ def _validate_ase_is_v3(ase): def _validate_ase_not_ilb(ase): - if ase.internal_load_balancing_mode != 0: + if (ase.internal_load_balancing_mode != 0) and (ase.internal_load_balancing_mode != "None"): raise ValidationError("Internal Load Balancer (ILB) App Service Environments not supported") 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 07da1e3a122..ed3c5c91e9b 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/custom.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/custom.py @@ -1755,6 +1755,27 @@ def create_functionapp_slot(cmd, resource_group_name, name, slot, configuration_ return result +def _resolve_storage_account_resource_group(cmd, name): + from azure.cli.command_modules.storage.operations.account import list_storage_accounts + accounts = [a for a in list_storage_accounts(cmd) if a.name == name] + if accounts: + return parse_resource_id(accounts[0].id).get("resource_group") + + +def _set_site_config_storage_keys(cmd, site_config): + from azure.cli.command_modules.storage._client_factory import cf_sa_for_keys + + for _, acct in site_config.azure_storage_accounts.items(): + if acct.access_key is None: + scf = cf_sa_for_keys(cmd.cli_ctx, None) + acct_rg = _resolve_storage_account_resource_group(cmd, acct.account_name) + keys = scf.list_keys(acct_rg, acct.account_name, logging_enable=False).keys + if keys: + key = keys[0] + logger.info("Retreived key %s", key.key_name) + acct.access_key = key.value + + def update_slot_configuration_from_source(cmd, client, resource_group_name, webapp, slot, configuration_source=None, deployment_container_image_name=None, docker_registry_server_password=None, docker_registry_server_user=None, docker_registry_server_url=None): @@ -1762,6 +1783,10 @@ def update_slot_configuration_from_source(cmd, client, resource_group_name, weba clone_from_prod = configuration_source.lower() == webapp.lower() site_config = get_site_configs(cmd, resource_group_name, webapp, None if clone_from_prod else configuration_source) + if site_config.azure_storage_accounts: + logger.warning("The configuration source has storage accounts. Looking up their access keys...") + _set_site_config_storage_keys(cmd, site_config) + _generic_site_operation(cmd.cli_ctx, resource_group_name, webapp, 'update_configuration', slot, site_config) @@ -1896,8 +1921,6 @@ def create_app_service_plan(cmd, resource_group_name, name, is_linux, hyper_v, p client = web_client_factory(cmd.cli_ctx) if app_service_environment: - if hyper_v: - raise ArgumentUsageError('Windows containers is not yet supported in app service environment') ase_list = client.app_service_environments.list() ase_found = False ase = None @@ -1910,6 +1933,8 @@ def create_app_service_plan(cmd, resource_group_name, name, is_linux, hyper_v, p if not ase_found: err_msg = "App service environment '{}' not found in subscription.".format(app_service_environment) raise ResourceNotFoundError(err_msg) + if hyper_v and ase.kind in ('ASEV1', 'ASEV2'): + raise ArgumentUsageError('Windows containers are only supported on v3 App Service Environments v3 or newer') else: # Non-ASE ase_def = None if location is None: diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands.py index c612acbbf5a..9140555fc72 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_webapp_commands.py @@ -2477,5 +2477,33 @@ def test_proxy_unsafe_ssl(self): self.assertEqual(http.connection_pool_kw['cert_reqs'], 'CERT_NONE') +class WebappSlotTest(ScenarioTest): + @live_only() # Uses storage account keys, so must be live_only to not fail credscan + @ResourceGroupPreparer(location=LINUX_ASP_LOCATION_WEBAPP) + def test_slot_create_from_configuration_source(self, resource_group): + webapp_name = self.create_random_name(prefix='webapp-e2e', length=24) + plan = self.create_random_name(prefix='webapp-e2e-plan', length=24) + storage_account = self.create_random_name(prefix='storage', length=24) + container = self.create_random_name(prefix='container', length=24) + store_id = "store" + store_type = "AzureBlob" + + self.cmd(f'appservice plan create -g {resource_group} -n {plan} --sku S1 --is-linux') + self.cmd(f'webapp create -g {resource_group} -n {webapp_name} --plan {plan} --runtime "python|3.9"') + self.cmd(f'storage account create -n {storage_account} -g {resource_group} --location {LINUX_ASP_LOCATION_WEBAPP}') + self.cmd(f"storage container create -n {container} -g {resource_group} --account-name {storage_account}") + key = self.cmd(f"storage account keys list -n {storage_account}").get_output_in_json()[0]["value"] + self.cmd(f'webapp config storage-account add -g {resource_group} -n {webapp_name} -k {key} -a {storage_account} -i {store_id} --sn {container} -t {store_type}') + + self.cmd(f'webapp deployment slot create -g {resource_group} -n {webapp_name} -s "slot" --configuration-source {webapp_name}') + + self.cmd(f'webapp show -g {resource_group} -n {webapp_name} -s "slot"', checks=[ + JMESPathCheck(f"siteConfig.azureStorageAccounts.{store_id}.accessKey", None), + JMESPathCheck(f"siteConfig.azureStorageAccounts.{store_id}.accountName", storage_account), + JMESPathCheck(f"siteConfig.azureStorageAccounts.{store_id}.state", "Ok"), + JMESPathCheck(f"siteConfig.azureStorageAccounts.{store_id}.type", store_type), + ]) + + if __name__ == '__main__': unittest.main()