Skip to content

Commit

Permalink
[AppService] Add support for v3 function apps and node 12. (#11987)
Browse files Browse the repository at this point in the history
* [functionapp] Add support for v3 function apps and node 12.

* Changed --version to --functions-version to help clarify version flags. Added functions version to invalid runtime version error.

* Fixed styling.
  • Loading branch information
gzuber committed Feb 13, 2020
1 parent 6957c33 commit 9ac96e9
Show file tree
Hide file tree
Showing 8 changed files with 2,219 additions and 43 deletions.
4 changes: 4 additions & 0 deletions src/azure-cli/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Release History

* Azure Stack: surface commands under the profile of 2019-03-01-hybrid
* functionapp: Add ability to create Java function apps in Linux
* functionapp: Added --functions-version property to 'az functionapp create'
* functionapp: Added support for node 12 for v3 function apps
* functionapp: Added support for python 3.8 for v3 function apps
* functionapp: Changed python default version to 3.7 for v2 and v3 function apps

**ARM**

Expand Down
71 changes: 52 additions & 19 deletions src/azure-cli/azure/cli/command_modules/appservice/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# --------------------------------------------------------------------------------------------

NODE_VERSION_DEFAULT = "10.14"
NODE_VERSION_DEFAULT_FUNCTIONAPP = "~10"
NETCORE_VERSION_DEFAULT = "2.2"
DOTNET_VERSION_DEFAULT = "4.7"
PYTHON_VERSION_DEFAULT = "3.7"
Expand All @@ -19,26 +18,60 @@
NETCORE_VERSIONS = ['1.0', '1.1', '2.1', '2.2']
DOTNET_VERSIONS = ['3.5', '4.7']
LINUX_SKU_DEFAULT = "P1V2"
RUNTIME_TO_DEFAULT_VERSION = {
'node': '8',
'dotnet': '2',
'python': '3.6',
'java': '8'
FUNCTIONS_VERSIONS_FUNCTIONAPP = ['2', '3']
# functions version : default node version
NODE_VERSION_DEFAULT_FUNCTIONAPP = {
'2': '~10',
'3': '~12'
}

RUNTIME_TO_IMAGE_FUNCTIONAPP = {
'node': {
'8': 'mcr.microsoft.com/azure-functions/node:2.0-node8-appservice',
'10': 'mcr.microsoft.com/azure-functions/node:2.0-node10-appservice'
},
'python': {
'3.6': 'mcr.microsoft.com/azure-functions/python:2.0-python3.6-appservice',
'3.7': 'mcr.microsoft.com/azure-functions/python:2.0-python3.7-appservice'
# functions version -> runtime : default runtime version
RUNTIME_TO_DEFAULT_VERSION_FUNCTIONAPP = {
'2': {
'node': '8',
'dotnet': '2',
'python': '3.7',
'java': '8'
},
'dotnet': {
'2': 'mcr.microsoft.com/azure-functions/dotnet:2.0-appservice'
'3': {
'node': '12',
'dotnet': '3',
'python': '3.7',
'java': '8'
}
}
# functions version -> runtime -> runtime version : container image
RUNTIME_TO_IMAGE_FUNCTIONAPP = {
'2': {
'node': {
'8': 'mcr.microsoft.com/azure-functions/node:2.0-node8-appservice',
'10': 'mcr.microsoft.com/azure-functions/node:2.0-node10-appservice'
},
'python': {
'3.6': 'mcr.microsoft.com/azure-functions/python:2.0-python3.6-appservice',
'3.7': 'mcr.microsoft.com/azure-functions/python:2.0-python3.7-appservice'
},
'dotnet': {
'2': 'mcr.microsoft.com/azure-functions/dotnet:2.0-appservice'
},
'java': {
'8': 'mcr.microsoft.com/azure-functions/java:2.0-java8-appservice'
}
},
'java': {
'8': 'mcr.microsoft.com/azure-functions/java:2.0-java8-appservice'
'3': {
'node': {
'10': 'mcr.microsoft.com/azure-functions/node:3.0-node10-appservice',
'12': 'mcr.microsoft.com/azure-functions/node:3.0-node12-appservice'
},
'python': {
'3.6': 'mcr.microsoft.com/azure-functions/python:3.0-python3.6-appservice',
'3.7': 'mcr.microsoft.com/azure-functions/python:3.0-python3.7-appservice',
'3.8': 'mcr.microsoft.com/azure-functions/python:3.0-python3.8-appservice'
},
'dotnet': {
'3': 'mcr.microsoft.com/azure-functions/dotnet:3.0-appservice'
},
'java': {
'8': 'mcr.microsoft.com/azure-functions/java:3.0-java8-appservice'
}
}
}
15 changes: 12 additions & 3 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from azure.mgmt.web.models import DatabaseType, ConnectionStringType, BuiltInAuthenticationProvider, AzureStorageType

from ._completers import get_hostname_completion_list
from ._constants import RUNTIME_TO_IMAGE_FUNCTIONAPP
from ._constants import FUNCTIONS_VERSIONS_FUNCTIONAPP, RUNTIME_TO_IMAGE_FUNCTIONAPP
from ._validators import (validate_timeout_value, validate_site_create, validate_asp_create,
validate_add_vnet, validate_front_end_scale_factor, validate_ase_create)

Expand Down Expand Up @@ -50,9 +50,17 @@ def load_arguments(self, _):
isolated_sku_arg_type = CLIArgumentType(help='The Isolated pricing tiers, e.g., I1 (Isolated Small), I2 (Isolated Medium), I3 (Isolated Large)',
arg_type=get_enum_type(['I1', 'I2', 'I3']))

# combine all runtime versions for all functions versions
functionapp_runtime_to_version = {}
for functions_version in RUNTIME_TO_IMAGE_FUNCTIONAPP.values():
for runtime, val in functions_version.items():
functionapp_runtime_to_version[runtime] = functionapp_runtime_to_version.get(runtime, set()).union(val.keys())

functionapp_runtime_to_version_texts = []
for runtime, val in RUNTIME_TO_IMAGE_FUNCTIONAPP.items():
functionapp_runtime_to_version_texts.append(runtime + ' -> [' + ', '.join(val.keys()) + ']')
for runtime, runtime_versions in functionapp_runtime_to_version.items():
runtime_versions_list = list(runtime_versions)
runtime_versions_list.sort(key=float)
functionapp_runtime_to_version_texts.append(runtime + ' -> [' + ', '.join(runtime_versions_list) + ']')

# use this hidden arg to give a command the right instance, that functionapp commands
# work on function app and webapp ones work on web app
Expand Down Expand Up @@ -460,6 +468,7 @@ def load_arguments(self, _):
help='Provide a string value of a Storage Account in the provided Resource Group. Or Resource ID of a Storage Account in a different Resource Group')
c.argument('consumption_plan_location', options_list=['--consumption-plan-location', '-c'],
help="Geographic location where Function App will be hosted. Use `az functionapp list-consumption-locations` to view available locations.")
c.argument('functions_version', help='The functions app version.', arg_type=get_enum_type(FUNCTIONS_VERSIONS_FUNCTIONAPP))
c.argument('runtime', help='The functions runtime stack.', arg_type=get_enum_type(set(LINUX_RUNTIMES).union(set(WINDOWS_RUNTIMES))))
c.argument('runtime_version', help='The version of the functions runtime stack. '
'Allowed values for each --runtime are: ' + ', '.join(functionapp_runtime_to_version_texts))
Expand Down
55 changes: 34 additions & 21 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
should_create_new_rg, set_location, does_app_already_exist, get_profile_username,
get_plan_to_use, get_lang_from_content, get_rg_to_use, get_sku_to_use,
detect_os_form_src)
from ._constants import (RUNTIME_TO_DEFAULT_VERSION, NODE_VERSION_DEFAULT_FUNCTIONAPP,
from ._constants import (RUNTIME_TO_DEFAULT_VERSION_FUNCTIONAPP, NODE_VERSION_DEFAULT_FUNCTIONAPP,
RUNTIME_TO_IMAGE_FUNCTIONAPP, NODE_VERSION_DEFAULT)

logger = get_logger(__name__)
Expand Down Expand Up @@ -2301,12 +2301,17 @@ def validate_range_of_int_flag(flag_name, value, min_val, max_val):


def create_function(cmd, resource_group_name, name, storage_account, plan=None,
os_type=None, runtime=None, runtime_version=None, consumption_plan_location=None,
app_insights=None, app_insights_key=None, disable_app_insights=None, deployment_source_url=None,
os_type=None, functions_version=None, runtime=None, runtime_version=None,
consumption_plan_location=None, app_insights=None, app_insights_key=None,
disable_app_insights=None, deployment_source_url=None,
deployment_source_branch='master', deployment_local_git=None,
docker_registry_server_password=None, docker_registry_server_user=None,
deployment_container_image_name=None, tags=None):
# pylint: disable=too-many-statements, too-many-branches
if functions_version is None:
logger.warning("No functions version specified so defaulting to 2. In the future, specifying a version will "
"be required. To create a 2.x function you would pass in the flag `--functions_version 2`")
functions_version = '2'
if deployment_source_url and deployment_local_git:
raise CLIError('usage error: --deployment-source-url <url> | --deployment-local-git')
if bool(plan) == bool(consumption_plan_location):
Expand Down Expand Up @@ -2360,22 +2365,19 @@ def create_function(cmd, resource_group_name, name, storage_account, plan=None,
if runtime_version is not None:
if runtime is None:
raise CLIError('Must specify --runtime to use --runtime-version')
allowed_versions = RUNTIME_TO_IMAGE_FUNCTIONAPP[runtime].keys()
allowed_versions = RUNTIME_TO_IMAGE_FUNCTIONAPP[functions_version][runtime].keys()
if runtime_version not in allowed_versions:
raise CLIError('--runtime-version {} is not supported for the selected --runtime {}. '
'Supported versions are: {}'
.format(runtime_version, runtime, ', '.join(allowed_versions)))
raise CLIError('--runtime-version {} is not supported for the selected --runtime {} and '
'--functions_version {}. Supported versions are: {}'
.format(runtime_version, runtime, functions_version, ', '.join(allowed_versions)))

con_string = _validate_and_get_connection_string(cmd.cli_ctx, resource_group_name, storage_account)

if is_linux:
functionapp_def.kind = 'functionapp,linux'
functionapp_def.reserved = True
is_consumption = consumption_plan_location is not None
if is_consumption:
site_config.app_settings.append(NameValuePair(name='FUNCTIONS_EXTENSION_VERSION', value='~2'))
else:
site_config.app_settings.append(NameValuePair(name='FUNCTIONS_EXTENSION_VERSION', value='~2'))
if not is_consumption:
site_config.app_settings.append(NameValuePair(name='MACHINEKEY_DecryptionKey',
value=str(hexlify(urandom(32)).decode()).upper()))
if deployment_container_image_name:
Expand All @@ -2389,18 +2391,23 @@ def create_function(cmd, resource_group_name, name, storage_account, plan=None,
else:
site_config.app_settings.append(NameValuePair(name='WEBSITES_ENABLE_APP_SERVICE_STORAGE',
value='true'))
if runtime not in RUNTIME_TO_IMAGE_FUNCTIONAPP.keys():
if runtime not in RUNTIME_TO_IMAGE_FUNCTIONAPP[functions_version].keys():
raise CLIError("An appropriate linux image for runtime:'{}' was not found".format(runtime))
if deployment_container_image_name is None:
site_config.linux_fx_version = _get_linux_fx_functionapp(is_consumption, runtime, runtime_version)
site_config.linux_fx_version = _get_linux_fx_functionapp(is_consumption,
functions_version,
runtime,
runtime_version)
else:
functionapp_def.kind = 'functionapp'
site_config.app_settings.append(NameValuePair(name='FUNCTIONS_EXTENSION_VERSION', value='~2'))
# adding appsetting to site to make it a function
site_config.app_settings.append(NameValuePair(name='FUNCTIONS_EXTENSION_VERSION',
value=_get_extension_version_functionapp(functions_version)))
site_config.app_settings.append(NameValuePair(name='AzureWebJobsStorage', value=con_string))
site_config.app_settings.append(NameValuePair(name='AzureWebJobsDashboard', value=con_string))
site_config.app_settings.append(NameValuePair(name='WEBSITE_NODE_DEFAULT_VERSION',
value=_get_website_node_version_functionapp(runtime,
value=_get_website_node_version_functionapp(functions_version,
runtime,
runtime_version)))

# If plan is not consumption or elastic premium, we need to set always on
Expand Down Expand Up @@ -2452,21 +2459,27 @@ def create_function(cmd, resource_group_name, name, storage_account, plan=None,
return functionapp


def _get_linux_fx_functionapp(is_consumption, runtime, runtime_version):
def _get_extension_version_functionapp(functions_version):
if functions_version is not None:
return '~{}'.format(functions_version)
return '~2'


def _get_linux_fx_functionapp(is_consumption, functions_version, runtime, runtime_version):
if runtime_version is None:
runtime_version = RUNTIME_TO_DEFAULT_VERSION[runtime]
runtime_version = RUNTIME_TO_DEFAULT_VERSION_FUNCTIONAPP[functions_version][runtime]
if is_consumption:
return '{}|{}'.format(runtime.upper(), runtime_version)
# App service or Elastic Premium
return _format_fx_version(RUNTIME_TO_IMAGE_FUNCTIONAPP[runtime][runtime_version])
return _format_fx_version(RUNTIME_TO_IMAGE_FUNCTIONAPP[functions_version][runtime][runtime_version])


def _get_website_node_version_functionapp(runtime, runtime_version):
def _get_website_node_version_functionapp(functions_version, runtime, runtime_version):
if runtime is None or runtime != 'node':
return NODE_VERSION_DEFAULT_FUNCTIONAPP
return NODE_VERSION_DEFAULT_FUNCTIONAPP[functions_version]
if runtime_version is not None:
return '~{}'.format(runtime_version)
return NODE_VERSION_DEFAULT_FUNCTIONAPP
return NODE_VERSION_DEFAULT_FUNCTIONAPP[functions_version]


def try_create_application_insights(cmd, functionapp):
Expand Down
Loading

0 comments on commit 9ac96e9

Please sign in to comment.