Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/command_modules/azure-cli-appservice/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Release History
* webapp, functionapp: Updating to use the new Python SDK version
* appservice: adminSiteName property of SKU object is deprecated
* functionapp: add ability to switch a plan underneath a function app using `az functionapp update --plan`
* functionapp: add support for azure functions premium plan scale out settings

0.2.16
++++++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@

helps['functionapp config set'] = """
type: command
short-summary: Set the web app's configuration.
short-summary: Set the function app's configuration.
"""

helps['functionapp config show'] = """
type: command
short-summary: Get the details of a web app's configuration.
short-summary: Get the details of a function app's configuration.
"""

helps['functionapp config ssl'] = """
Expand Down Expand Up @@ -416,14 +416,46 @@

helps['functionapp plan create'] = """
type: command
short-summary: Create an App Service Plan for an Azure Function
short-summary: Create an App Service Plan for an Azure Function.
examples:
- name: Create an elastic premium app service plan with burst out capability up to 10 instances.
text: >
az functionapp plan create -g MyResourceGroup -n MyPlan --min-instances 1 --max-burst 10 --sku EP1
- name: Create a basic app service plan.
text: >
az functionapp plan create -g MyResourceGroup -n MyPlan --sku B1
- name: Create a standard app service plan with with four workers.
"""

helps['functionapp plan update'] = """
type: command
short-summary: Update an App Service plan for an Azure Function.
examples:
- name: Update an app service plan to EP2 sku with twenty maximum workers.
text: >
az functionapp plan update -g MyResourceGroup -n MyPlan --max-burst 20 --sku EP2
"""

helps['functionapp plan delete'] = """
type: command
short-summary: Delete an App Service Plan.
"""

helps['functionapp plan list'] = """
type: command
short-summary: List App Service Plans.
examples:
- name: List all Elastic Premium 1 tier App Service plans.
text: >
az functionapp plan create -g MyResourceGroup -n MyPlan --number-of-workers 4 --sku S1
az functionapp plan list --query "[?sku.tier=='EP1']"
"""

helps['functionapp plan show'] = """
type: command
short-summary: Get the App Service Plans for a resource group or a set of resource groups.
examples:
- name: Get the app service plans for a resource group or a set of resource groups. (autogenerated)
text: az functionapp plan show --name MyAppServicePlan --resource-group MyResourceGroup
Copy link
Contributor

Choose a reason for hiding this comment

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

Please take care of the extra white-spaces here in the next PR. I am merging it since we are running out of the time for the current release

crafted: true
"""

helps['functionapp restart'] = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def load_arguments(self, _):
configured_default='appserviceplan', id_part='name')
c.argument('number_of_workers', help='Number of workers to be allocated.', type=int, default=1)
c.argument('admin_site_name', help='The name of the admin web app.', deprecate_info=c.deprecate(expiration='0.2.17'))
c.ignore('max_burst')

with self.argument_context('appservice plan create') as c:
c.argument('name', options_list=['--name', '-n'], help="Name of the new app service plan", completer=None)
Expand Down Expand Up @@ -205,6 +206,9 @@ def load_arguments(self, _):
c.argument('windows_fx_version', help="(Preview) a docker image name used for your windows container web app, e.g., microsoft/nanoserver:ltsc2016")
if scope == 'functionapp':
c.ignore('windows_fx_version')
c.argument('reserved_instance_count', options_list=['--prewarmed-instance-count'], help="Number of pre-warmed instances a function app has")
if scope == 'webapp':
c.ignore('reserved_instance_count')
c.argument('java_version', help="The version used to run your web app if using Java, e.g., '1.7' for Java 7, '1.8' for Java 8")
c.argument('java_container', help="The java container, e.g., Tomcat, Jetty")
c.argument('java_container_version', help="The version of the java container, e.g., '8.0.23' for Tomcat")
Expand Down Expand Up @@ -392,14 +396,22 @@ def load_arguments(self, _):
c.argument('name', arg_type=name_arg_type, help='The name of the app service plan',
completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
configured_default='appserviceplan', id_part='name')
c.argument('sku', required=True, help='The SKU of the app service plan.')
c.argument('number_of_workers', help='The number of workers for the app service plan.')
c.argument('is_linux', arg_type=get_three_state_flag(return_label=True), required=False, help='host function app on Linux worker')
c.argument('number_of_workers', options_list=['--number-of-workers', '--min-instances'],
help='The number of workers for the app service plan.')
c.argument('max_burst',
help='The maximum number of elastic workers for the plan.')
c.argument('tags', arg_type=tags_type)

with self.argument_context('functionapp update') as c:
c.argument('plan', required=False, help='The name or resource id of the plan to update the functionapp with.')

with self.argument_context('functionapp plan create') as c:
c.argument('sku', required=True, help='The SKU of the app service plan.')

with self.argument_context('functionapp plan update') as c:
c.argument('sku', required=False, help='The SKU of the app service plan.')

with self.argument_context('functionapp devops-build create') as c:
c.argument('functionapp_name', help="Name of the Azure Function App that you want to use", required=False)
c.argument('organization_name', help="Name of the Azure DevOps organization that you want to use", required=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,13 @@ def load_command_table(self, _):
g.custom_command('remove', 'remove_cors')
g.custom_command('show', 'show_cors')

with self.command_group('functionapp plan') as g:
with self.command_group('functionapp plan', appservice_plan_sdk) as g:
g.custom_command('create', 'create_functionapp_app_service_plan')
g.generic_update_command('update', custom_func_name='update_functionapp_app_service_plan',
setter_arg_name='app_service_plan')
g.command('delete', 'delete', confirmation=True)
g.custom_command('list', 'list_app_service_plans')
g.show_command('show', 'get')

with self.command_group('functionapp deployment container') as g:
g.custom_command('config', 'enable_cd')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,9 @@ def validate_plan_switch_compatibility(client, src_functionapp_instance, dest_pl
src_parse_result['name'])
if src_plan_info is None:
raise CLIError('Could not determine the current plan of the functionapp')
elif not (is_plan_consumption(src_plan_info) or is_plan_Elastic_Premium(src_plan_info)):
elif not (is_plan_consumption(src_plan_info) or is_plan_elastic_premium(src_plan_info)):
raise CLIError('Your functionapp is not using a Consumption or an Elastic Premium plan. ' + general_switch_msg)
if not (is_plan_consumption(dest_plan_instance) or is_plan_Elastic_Premium(dest_plan_instance)):
if not (is_plan_consumption(dest_plan_instance) or is_plan_elastic_premium(dest_plan_instance)):
raise CLIError('You are trying to move to a plan that is not a Consumption or an Elastic Premium plan. ' +
general_switch_msg)

Expand Down Expand Up @@ -669,8 +669,8 @@ def _get_linux_multicontainer_encoded_config_from_file(file_name):
# for any modifications to the non-optional parameters, adjust the reflection logic accordingly
# in the method
def update_site_configs(cmd, resource_group_name, name, slot=None,
linux_fx_version=None, windows_fx_version=None, php_version=None, python_version=None, # pylint: disable=unused-argument
net_framework_version=None, # pylint: disable=unused-argument
linux_fx_version=None, windows_fx_version=None, reserved_instance_count=None, php_version=None, # pylint: disable=unused-argument
python_version=None, net_framework_version=None, # pylint: disable=unused-argument
java_version=None, java_container=None, java_container_version=None, # pylint: disable=unused-argument
remote_debugging_enabled=None, web_sockets_enabled=None, # pylint: disable=unused-argument
always_on=None, auto_heal_enabled=None, # pylint: disable=unused-argument
Expand All @@ -687,14 +687,21 @@ def update_site_configs(cmd, resource_group_name, name, slot=None,
else:
delete_app_settings(cmd, resource_group_name, name, ["WEBSITES_ENABLE_APP_SERVICE_STORAGE"])

if reserved_instance_count is not None:
reserved_instance_count = validate_range_of_int_flag('--prewarmed-instance-count', reserved_instance_count,
min_val=0, max_val=20)
import inspect
frame = inspect.currentframe()
bool_flags = ['remote_debugging_enabled', 'web_sockets_enabled', 'always_on',
'auto_heal_enabled', 'use32_bit_worker_process', 'http20_enabled']
int_flags = ['reserved_instance_count']
# note: getargvalues is used already in azure.cli.core.commands.
# and no simple functional replacement for this deprecating method for 3.5
args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method

for arg in args[3:]:
if arg in int_flags and values[arg] is not None:
values[arg] = validate_and_convert_to_int(arg, values[arg])
if arg != 'generic_configurations' and values.get(arg, None):
setattr(configs, arg, values[arg] if arg not in bool_flags else values[arg] == 'true')

Expand Down Expand Up @@ -1192,6 +1199,19 @@ def update_app_service_plan(instance, sku=None, number_of_workers=None):
return instance


def update_functionapp_app_service_plan(instance, sku=None, number_of_workers=None, max_burst=None):
instance = update_app_service_plan(instance, sku, number_of_workers)
if max_burst is not None:
if not is_plan_elastic_premium(instance):
raise CLIError("Usage error: --max-burst is only supported for Elastic Premium (EP) plans")
max_burst = validate_range_of_int_flag('--max-burst', max_burst, min_val=0, max_val=20)
instance.maximum_elastic_worker_count = max_burst
if number_of_workers is not None:
number_of_workers = validate_range_of_int_flag('--number-of-workers / --min-instances',
number_of_workers, min_val=0, max_val=20)
return update_app_service_plan(instance, sku, number_of_workers)


def show_backup_configuration(cmd, resource_group_name, webapp_name, slot=None):
try:
return _generic_site_operation(cmd.cli_ctx, resource_group_name, webapp_name,
Expand Down Expand Up @@ -1925,10 +1945,24 @@ def get_app_insights_key(cli_ctx, resource_group, name):


def create_functionapp_app_service_plan(cmd, resource_group_name, name, is_linux, sku,
number_of_workers=None, location=None, tags=None):
# This command merely shadows 'az appservice plan create' except with a few parameters
return create_app_service_plan(cmd, resource_group_name, name, is_linux, hyper_v=None,
sku=sku, number_of_workers=number_of_workers, location=location, tags=tags)
number_of_workers=None, max_burst=None, location=None, tags=None):
sku = _normalize_sku(sku)
tier = get_sku_name(sku)
if max_burst is not None:
if tier.lower() != "elasticpremium":
raise CLIError("Usage error: --max-burst is only supported for Elastic Premium (EP) plans")
max_burst = validate_range_of_int_flag('--max-burst', max_burst, min_val=0, max_val=20)
if number_of_workers is not None:
number_of_workers = validate_range_of_int_flag('--number-of-workers / --min-elastic-worker-count',
number_of_workers, min_val=0, max_val=20)
client = web_client_factory(cmd.cli_ctx)
if location is None:
location = _get_location_from_resource_group(cmd.cli_ctx, resource_group_name)
sku_def = SkuDescription(tier=tier, name=sku, capacity=number_of_workers)
plan_def = AppServicePlan(location=location, tags=tags, sku=sku_def,
reserved=(is_linux or None), maximum_elastic_worker_count=max_burst,
hyper_v=None, name=name)
return client.app_service_plans.create_or_update(resource_group_name, name, plan_def)


def is_plan_consumption(plan_info):
Expand All @@ -1938,13 +1972,28 @@ def is_plan_consumption(plan_info):
return False


def is_plan_Elastic_Premium(plan_info):
def is_plan_elastic_premium(plan_info):
if isinstance(plan_info, AppServicePlan):
if isinstance(plan_info.sku, SkuDescription):
return plan_info.sku.tier == 'ElasticPremium'
return False


def validate_and_convert_to_int(flag, val):
try:
return int(val)
except ValueError:
raise CLIError("Usage error: {} is expected to have an int value.".format(flag))


def validate_range_of_int_flag(flag_name, value, min_val, max_val):
value = validate_and_convert_to_int(flag_name, value)
if min_val > value or value > max_val:
raise CLIError("Usage error: {} is expected to be between {} and {} (inclusive)".format(flag_name, min_val,
max_val))
return value


def create_function(cmd, resource_group_name, name, storage_account, plan=None,
os_type=None, runtime=None, consumption_plan_location=None,
app_insights=None, app_insights_key=None, deployment_source_url=None,
Expand Down Expand Up @@ -2030,7 +2079,7 @@ def create_function(cmd, resource_group_name, name, storage_account, plan=None,
site_config.app_settings.append(NameValuePair(name='AzureWebJobsDashboard', value=con_string))
site_config.app_settings.append(NameValuePair(name='WEBSITE_NODE_DEFAULT_VERSION', value='8.11.1'))

if consumption_plan_location is None and not is_plan_Elastic_Premium(plan_info):
if consumption_plan_location is None and not is_plan_elastic_premium(plan_info):
site_config.always_on = True
else:
site_config.app_settings.append(NameValuePair(name='WEBSITE_CONTENTAZUREFILECONNECTIONSTRING',
Expand Down
Loading