Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more flex-consumption locations #19

Closed
wants to merge 7 commits into from
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
31 changes: 31 additions & 0 deletions src/azure-cli/azure/cli/command_modules/appservice/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,34 @@ def __init__(self):
"14-lts-is-ending-on-30-april-2023/",
"node|16": "https://azure.microsoft.com/en-us/updates/node16support/"
}

FLEX_RUNTIMES = [
{
'runtime': 'dotnet-isolated',
'version': '6'
},
{
'runtime': 'java',
'version': '17.0'
},
{
'runtime': 'node',
'version': '18'
},
{
'runtime': 'python',
'version': '3.10'
},
{
'runtime': 'powershell',
'version': '7.2'
}
]

FLEX_SUBNET_DELEGATION = "Microsoft.App/environments"

DEFAULT_INSTANCE_SIZE = 2048

DEPLOYMENT_STORAGE_AUTH_TYPES = ['systemAssignedIdentity', 'userAssignedIdentity', 'storageAccountConnectionString']

STORAGE_BLOB_DATA_CONTRIBUTOR_ROLE_ID = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
52 changes: 52 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 @@ -380,6 +380,52 @@
text: az functionapp config ssl create --resource-group MyResourceGroup --name MyWebapp --hostname cname.mycustomdomain.com
"""

helps['functionapp scale config'] = """
type: group
short-summary: Manage a function app's scale configuration.
"""

helps['functionapp scale config set'] = """
type: command
short-summary: Update an existing function app's scale configuration.
examples:
- name: Set the function app's instance memory configuration.
text: az functionapp scale config set --name MyFunctionApp --resource-group MyResourceGroup --instance-memory 2048
- name: Set the function app's maximum instance count configuration.
text: az functionapp scale config set --name MyFunctionApp --resource-group MyResourceGroup --maximum-instance-count 5
- name: Set the function app's trigger configuration.
text: az functionapp scale config set --name MyFunctionApp --resource-group MyResourceGroup --trigger-type http --trigger-settings perInstanceConcurrency=1
"""

helps['functionapp scale config show'] = """
type: command
short-summary: Get the details of a function app's scale configuration.
examples:
- name: Get the details of a function app's scale configuration.
text: az functionapp scale config show --name MyFunctionApp --resource-group MyResourceGroup
"""

helps['functionapp scale config always-ready'] = """
type: group
short-summary: Manage the always-ready settings in the scale configuration.
"""

helps['functionapp scale config always-ready delete'] = """
type: command
short-summary: Delete always-ready settings in the scale configuration.
examples:
- name: Delete always-ready setings in the scale configuration.
text: az functionapp scale config always-ready delete --name MyFunctionApp --resource-group MyResourceGroup --setting-names key1 key2
"""

helps['functionapp scale config always-ready set'] = """
type: command
short-summary: Add or update existing always-ready settings in the scale configuration.
examples:
- name: Add or update existing always-ready settings in the scale configuration.
text: az functionapp scale config always-ready set --name MyFunctionApp --resource-group MyResourceGroup --settings key1=value1 key2=value2
"""

helps['functionapp cors'] = """
type: group
short-summary: Manage Cross-Origin Resource Sharing (CORS)
Expand Down Expand Up @@ -865,6 +911,12 @@
short-summary: List available locations for running function apps.
"""


helps['functionapp list-flexconsumption-locations'] = """
type: command
short-summary: List available locations for running function apps on the Flex Consumption plan.
"""

helps['functionapp plan'] = """
type: group
short-summary: Manage App Service Plans for an Azure Function
Expand Down
28 changes: 23 additions & 5 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from azure.mgmt.web.models import DatabaseType, ConnectionStringType, BuiltInAuthenticationProvider, AzureStorageType

from ._completers import get_hostname_completion_list
from ._constants import (FUNCTIONS_VERSIONS, LOGICAPPS_NODE_RUNTIME_VERSIONS, WINDOWS_OS_NAME, LINUX_OS_NAME)
from ._constants import (FUNCTIONS_VERSIONS, LOGICAPPS_NODE_RUNTIME_VERSIONS, WINDOWS_OS_NAME, LINUX_OS_NAME,
DEPLOYMENT_STORAGE_AUTH_TYPES)

from ._validators import (validate_timeout_value, validate_site_create, validate_asp_create,
validate_front_end_scale_factor, validate_ase_create, validate_ip_address,
Expand Down Expand Up @@ -412,6 +413,16 @@ def load_arguments(self, _):
c.argument('cpu', type=float, help="Required CPU in cores from 0.5 to 2.0.", is_preview=True)
c.argument('memory', help="Required momory from 1.0 to 4.0 ending with ""Gi"" e.g. 1.0Gi, ", is_preview=True)

with self.argument_context('functionapp scale config') as c:
c.argument('maximum_instance_count', type=int, help="The maximum number of instances.", is_preview=True)
c.argument('instance_memory', type=int, help="The instance memory size in MB.", is_preview=True)
c.argument('trigger_type', help="The type of trigger.", is_preview=True)
c.argument('trigger_settings', nargs='+', help="space-separated settings for the trigger type in the format `<name>=<value>`", is_preview=True)

with self.argument_context('functionapp scale config always-ready') as c:
c.argument('setting_names', nargs='+', help="space-separated always-ready setting names", is_preview=True)
c.argument('settings', nargs='+', help="space-separated configuration for the number of pre-allocated instances in the format `<name>=<value>`", is_preview=True)

with self.argument_context('webapp config connection-string list') as c:
c.argument('name', arg_type=webapp_name_arg_type, id_part=None)

Expand All @@ -428,7 +439,7 @@ def load_arguments(self, _):
c.argument('xml', options_list=['--xml'], required=False, help='retrieves the publishing profile details in XML format')
with self.argument_context('webapp deployment slot') as c:
c.argument('slot', help='the name of the slot')
c.argument('webapp', arg_type=name_arg_type, completer=get_resource_name_completion_list('Microsoft.Web/sites'),
c.argument('name', arg_type=name_arg_type, completer=get_resource_name_completion_list('Microsoft.Web/sites'),
help='Name of the webapp', id_part='name',
local_context_attribute=LocalContextAttribute(name='web_name', actions=[LocalContextAction.GET]))
c.argument('auto_swap_slot', help='target slot to auto swap', default='production')
Expand Down Expand Up @@ -766,10 +777,19 @@ def load_arguments(self, _):
c.argument('dapr_http_read_buffer_size', type=int, options_list=['--dapr-http-read-buffer-size', '--dhrbs'], help="Max size of http header read buffer in KB to handle when sending multi-KB headers.")
c.argument('dapr_log_level', help="The log level for the Dapr sidecar", arg_type=get_enum_type(DAPR_LOG_LEVELS))
c.argument('dapr_enable_api_logging', options_list=['--dapr-enable-api-logging', '--dal'], help="Enable/Disable API logging for the Dapr sidecar.", arg_type=get_three_state_flag(return_label=True))
c.argument('always_ready_instances', nargs='+', help="space-separated configuration for the number of pre-allocated instances in the format `<name>=<value>`", is_preview=True)
c.argument('maximum_instance_count', type=int, help="The maximum number of instances.", is_preview=True)
c.argument('instance_memory', type=int, help="The instance memory size in MB.", is_preview=True)
c.argument('flexconsumption_location', options_list=['--flexconsumption-location', '-f'],
help="Geographic location where function app will be hosted. Use `az functionapp list-flexconsumption-locations` to view available locations.", is_preview=True)
c.argument('workspace', help="Name of an existing log analytics workspace to be used for the application insights component")
c.argument('workload_profile_name', help="The workload profile name to run the container app on.", is_preview=True)
c.argument('cpu', type=float, help="The CPU in cores of the container app. e.g 0.75", is_preview=True)
c.argument('memory', help="The memory size of the container app. e.g. 1.0Gi, ", is_preview=True)
c.argument('deployment_storage_name', help="The deployment storage account name.", is_preview=True)
c.argument('deployment_storage_container_name', help="The deployment storage account container name.", is_preview=True)
c.argument('deployment_storage_auth_type', arg_type=get_enum_type(DEPLOYMENT_STORAGE_AUTH_TYPES), help="The deployment storage account authentication type.", is_preview=True)
c.argument('deployment_storage_auth_value', help="The deployment storage account authentication value. This is only applicable for the user-assigned managed identity authentication type.", is_preview=True)

with self.argument_context('functionapp cors credentials') as c:
c.argument('enable', help='enable/disable access-control-allow-credentials', arg_type=get_three_state_flag())
Expand Down Expand Up @@ -916,9 +936,7 @@ def load_arguments(self, _):
c.argument('xml', options_list=['--xml'], required=False, help='retrieves the publishing profile details in XML format')
with self.argument_context('functionapp deployment slot') as c:
c.argument('slot', help='the name of the slot')
# This is set to webapp to simply reuse webapp functions, without rewriting same functions for function apps.
# The help will still show "-n or --name", so it should not be a problem to do it this way
c.argument('webapp', arg_type=functionapp_name_arg_type,
c.argument('name', arg_type=functionapp_name_arg_type,
completer=get_resource_name_completion_list('Microsoft.Web/sites'),
help='Name of the function app', id_part='name')
c.argument('auto_swap_slot', help='target slot to auto swap', default='production')
Expand Down
47 changes: 42 additions & 5 deletions src/azure-cli/azure/cli/command_modules/appservice/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

from ._appservice_utils import _generic_site_operation
from ._client_factory import web_client_factory
from .utils import (_normalize_sku, get_sku_tier, _normalize_location, get_resource_name_and_group,
get_resource_if_exists, is_functionapp, is_logicapp, is_webapp, is_centauri_functionapp)
from .utils import (_normalize_sku, get_sku_tier, get_resource_name_and_group,
get_resource_if_exists, is_functionapp, is_logicapp, is_webapp, is_centauri_functionapp,
_normalize_location_for_vnet_integration)

from .aaz.latest.network import ListServiceTags
from .aaz.latest.network.vnet import List as VNetList, Show as VNetShow
Expand Down Expand Up @@ -159,6 +160,33 @@ def validate_functionapp_on_containerapp_site_config_show(cmd, namespace):
"Please use the following command instead: az functionapp config container show")


def validate_functionapp_on_flex_plan(cmd, namespace):
resource_group_name = namespace.resource_group_name
name = namespace.name
functionapp = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get')
parsed_plan_id = parse_resource_id(functionapp.server_farm_id)
client = web_client_factory(cmd.cli_ctx)
plan_info = client.app_service_plans.get(parsed_plan_id['resource_group'], parsed_plan_id['name'])
if plan_info is None:
raise ResourceNotFoundError('Could not determine the current plan of the functionapp')
if plan_info.sku.tier == 'FlexConsumption':
raise ValidationError('Invalid command. This is not currently supported for Azure Functions '
'on the Flex Consumption plan.')


def validate_is_flex_functionapp(cmd, namespace):
resource_group_name = namespace.resource_group_name
name = namespace.name
functionapp = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get')
parsed_plan_id = parse_resource_id(functionapp.server_farm_id)
client = web_client_factory(cmd.cli_ctx)
plan_info = client.app_service_plans.get(parsed_plan_id['resource_group'], parsed_plan_id['name'])
if plan_info is None:
raise ResourceNotFoundError('Could not determine the current plan of the functionapp')
if plan_info.sku.tier.lower() != 'flexconsumption':
raise ValidationError('This command is only valid for Azure Functions on the FlexConsumption plan.')


def validate_app_exists(cmd, namespace):
app = namespace.name
resource_group_name = namespace.resource_group_name
Expand Down Expand Up @@ -225,12 +253,13 @@ def validate_add_vnet(cmd, namespace):

webapp = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get', slot)

webapp_loc = _normalize_location(cmd, webapp.location)
vnet_loc = _normalize_location(cmd, vnet_loc)
webapp_loc = _normalize_location_for_vnet_integration(cmd, webapp.location)
vnet_loc = _normalize_location_for_vnet_integration(cmd, vnet_loc)

if vnet_loc != webapp_loc:
raise ValidationError("The app and the vnet resources are in different locations. "
"Cannot integrate a regional VNET to an app in a different region")
"Cannot integrate a regional VNET to an app in a different region"
"Web app location: {}. Vnet location: {}".format(webapp_loc, vnet_loc))


def validate_front_end_scale_factor(namespace):
Expand Down Expand Up @@ -403,6 +432,8 @@ def validate_vnet_integration(cmd, namespace):
if is_valid_resource_id(namespace.plan):
parse_result = parse_resource_id(namespace.plan)
plan_info = client.app_service_plans.get(parse_result['resource_group'], parse_result['name'])
elif _get_flexconsumption_location(namespace):
return
elif _get_consumption_plan_location(namespace):
raise ArgumentUsageError("Virtual network integration is not allowed for consumption plans.")
else:
Expand Down Expand Up @@ -480,6 +511,12 @@ def _get_environment(namespace):
return None


def _get_flexconsumption_location(namespace):
if hasattr(namespace, "flexconsumption_location"):
return namespace.flexconsumption_location
return None


def _get_consumption_plan_location(namespace):
if hasattr(namespace, "consumption_plan_location"):
return namespace.consumption_plan_location
Expand Down
37 changes: 23 additions & 14 deletions src/azure-cli/azure/cli/command_modules/appservice/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
validate_functionapp_on_containerapp_site_config_set,
validate_functionapp_on_containerapp_site_config_show,
validate_functionapp_on_containerapp_container_settings_delete,
validate_functionapp_on_containerapp_update,
validate_functionapp)
validate_functionapp_on_containerapp_update, validate_functionapp_on_flex_plan,
validate_functionapp, validate_is_flex_functionapp)


def output_slots_in_table(slots):
Expand Down Expand Up @@ -325,6 +325,7 @@ def load_command_table(self, _):
g.custom_command('start', 'start_webapp')
g.custom_command('restart', 'restart_webapp')
g.custom_command('list-consumption-locations', 'list_consumption_locations')
g.custom_command('list-flexconsumption-locations', 'list_flexconsumption_locations')
g.custom_command('identity assign', 'assign_identity')
g.custom_show_command('identity show', 'show_identity')
g.custom_command('identity remove', 'remove_identity')
Expand All @@ -342,6 +343,14 @@ def load_command_table(self, _):
g.custom_command('set', 'update_app_settings_functionapp', exception_handler=ex_handler_factory())
g.custom_command('delete', 'delete_app_settings', exception_handler=ex_handler_factory())

with self.command_group('functionapp scale config') as g:
g.custom_command('show', 'get_scale_config', exception_handler=ex_handler_factory(), validator=validate_is_flex_functionapp)
g.custom_command('set', 'update_scale_config', exception_handler=ex_handler_factory(), validator=validate_is_flex_functionapp)

with self.command_group('functionapp scale config always-ready') as g:
g.custom_command('delete', 'delete_always_ready_settings', exception_handler=ex_handler_factory(), validator=validate_is_flex_functionapp)
g.custom_command('set', 'update_always_ready_settings', exception_handler=ex_handler_factory(), validator=validate_is_flex_functionapp)

with self.command_group('functionapp config hostname') as g:
g.custom_command('add', 'add_hostname', exception_handler=ex_handler_factory())
g.custom_command('list', 'list_hostnames')
Expand All @@ -359,21 +368,21 @@ def load_command_table(self, _):
g.custom_command('create', 'create_managed_ssl_cert', exception_handler=ex_handler_factory(), is_preview=True)

with self.command_group('functionapp deployment source') as g:
g.custom_command('config-local-git', 'enable_local_git')
g.custom_command('config-local-git', 'enable_local_git', validator=validate_functionapp_on_flex_plan)
g.custom_command('config-zip', 'enable_zip_deploy_functionapp')
g.custom_command('config', 'config_source_control', exception_handler=ex_handler_factory())
g.custom_command('sync', 'sync_site_repo', exception_handler=ex_handler_factory())
g.custom_show_command('show', 'show_source_control', exception_handler=ex_handler_factory())
g.custom_command('delete', 'delete_source_control', exception_handler=ex_handler_factory())
g.custom_command('config', 'config_source_control', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)
g.custom_command('sync', 'sync_site_repo', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)
g.custom_show_command('show', 'show_source_control', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)
g.custom_command('delete', 'delete_source_control', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)
g.custom_command('update-token', 'update_git_token', exception_handler=ex_handler_factory())

with self.command_group('functionapp deployment user', webclient_sdk) as g:
g.custom_command('set', 'set_deployment_user', exception_handler=ex_handler_factory())
g.custom_show_command('show', 'get_publishing_user')

with self.command_group('functionapp deployment') as g:
g.custom_command('list-publishing-profiles', 'list_publish_profiles')
g.custom_command('list-publishing-credentials', 'list_publishing_credentials')
g.custom_command('list-publishing-profiles', 'list_publish_profiles', validator=validate_functionapp_on_flex_plan)
g.custom_command('list-publishing-credentials', 'list_publishing_credentials', validator=validate_functionapp_on_flex_plan)

with self.command_group('functionapp cors') as g:
g.custom_command('add', 'add_cors')
Expand Down Expand Up @@ -402,11 +411,11 @@ def load_command_table(self, _):
g.custom_show_command('show', 'show_container_settings_functionapp')

with self.command_group('functionapp deployment slot') as g:
g.custom_command('list', 'list_slots', table_transformer=output_slots_in_table)
g.custom_command('delete', 'delete_slot')
g.custom_command('auto-swap', 'config_slot_auto_swap')
g.custom_command('swap', 'swap_slot', exception_handler=ex_handler_factory())
g.custom_command('create', 'create_functionapp_slot', exception_handler=ex_handler_factory())
g.custom_command('list', 'list_slots', table_transformer=output_slots_in_table, validator=validate_functionapp_on_flex_plan)
g.custom_command('delete', 'delete_slot', validator=validate_functionapp_on_flex_plan)
g.custom_command('auto-swap', 'config_slot_auto_swap', validator=validate_functionapp_on_flex_plan)
g.custom_command('swap', 'swap_slot', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)
g.custom_command('create', 'create_functionapp_slot', exception_handler=ex_handler_factory(), validator=validate_functionapp_on_flex_plan)

with self.command_group('functionapp keys') as g:
g.custom_command('set', 'update_host_key')
Expand Down
Loading
Loading