diff --git a/src/appservice-kube/HISTORY.rst b/src/appservice-kube/HISTORY.rst
index 2dc9269a52b..3e3b15de4b6 100644
--- a/src/appservice-kube/HISTORY.rst
+++ b/src/appservice-kube/HISTORY.rst
@@ -3,6 +3,10 @@
Release History
===============
+0.1.4
+++++++
+* Ensure compatibility of 'az webapp create' and 'az functionapp create' with CLI version 2.34.0
+
0.1.3
++++++
* Update functionapp runtimes and support v4 functionapps
diff --git a/src/appservice-kube/azext_appservice_kube/_constants.py b/src/appservice-kube/azext_appservice_kube/_constants.py
index a9a73120ee4..ee8eed8f8d8 100644
--- a/src/appservice-kube/azext_appservice_kube/_constants.py
+++ b/src/appservice-kube/azext_appservice_kube/_constants.py
@@ -13,83 +13,6 @@
KUBE_FUNCTION_APP_KIND = 'linux,kubernetes,functionapp'
KUBE_FUNCTION_CONTAINER_APP_KIND = 'linux,kubernetes,functionapp,container'
-LINUX_RUNTIMES = ['dotnet', 'node', 'python', 'java', 'powershell', 'dotnet-isolated', 'custom']
-WINDOWS_RUNTIMES = ['dotnet', 'node', 'java', 'powershell', 'dotnet-isolated', 'custom']
-
-NODE_VERSION_DEFAULT = "10.14"
-NODE_VERSION_NEWER = "12-lts"
-NODE_EXACT_VERSION_DEFAULT = "10.14.1"
-NETCORE_VERSION_DEFAULT = "2.2"
-DOTNET_VERSION_DEFAULT = "4.7"
-PYTHON_VERSION_DEFAULT = "3.7"
-NETCORE_RUNTIME_NAME = "dotnetcore"
-DOTNET_RUNTIME_NAME = "aspnet"
-NODE_RUNTIME_NAME = "node"
-PYTHON_RUNTIME_NAME = "python"
-OS_DEFAULT = "Windows"
-STATIC_RUNTIME_NAME = "static" # not an official supported runtime but used for CLI logic
-NODE_VERSIONS = ['4.4', '4.5', '6.2', '6.6', '6.9', '6.11', '8.0', '8.1', '8.9', '8.11', '10.1', '10.10', '10.14']
-PYTHON_VERSIONS = ['3.7', '3.6', '2.7']
-NETCORE_VERSIONS = ['1.0', '1.1', '2.1', '2.2']
-DOTNET_VERSIONS = ['3.5', '4.7']
-
-LINUX_SKU_DEFAULT = "P1V2"
-FUNCTIONS_VERSIONS = ['2', '3', '4']
-
-# functions version : default node version
-FUNCTIONS_VERSION_TO_DEFAULT_NODE_VERSION = {
- '2': '~10',
- '3': '~12'
-}
-# functions version -> runtime : default runtime version
-FUNCTIONS_VERSION_TO_DEFAULT_RUNTIME_VERSION = {
- '2': {
- 'node': '8',
- 'dotnet': '2',
- 'python': '3.7',
- 'java': '8'
- },
- '3': {
- 'node': '12',
- 'dotnet': '3',
- 'python': '3.7',
- 'java': '8'
- }
-}
-# functions version -> runtime : runtime versions
-FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS = {
- '2': {
- 'node': ['8', '10'],
- 'python': ['3.6', '3.7'],
- 'dotnet': ['2'],
- 'java': ['8']
- },
- '3': {
- 'node': ['10', '12'],
- 'python': ['3.6', '3.7', '3.8'],
- 'dotnet': ['3'],
- 'java': ['8']
- }
-}
-# dotnet runtime version : dotnet linuxFxVersion
-DOTNET_RUNTIME_VERSION_TO_DOTNET_LINUX_FX_VERSION = {
- '2': '2.2',
- '3': '3.1'
-}
-
MULTI_CONTAINER_TYPES = ['COMPOSE', 'KUBE']
OS_TYPES = ['Windows', 'Linux']
-
-CONTAINER_APPSETTING_NAMES = ['DOCKER_REGISTRY_SERVER_URL', 'DOCKER_REGISTRY_SERVER_USERNAME',
- 'DOCKER_REGISTRY_SERVER_PASSWORD', "WEBSITES_ENABLE_APP_SERVICE_STORAGE"]
-APPSETTINGS_TO_MASK = ['DOCKER_REGISTRY_SERVER_PASSWORD']
-
-
-FUNCTIONS_STACKS_API_JSON_PATHS = {
- 'windows': os.path.abspath(os.path.join(os.path.abspath(__file__), '../resources/WindowsFunctionsStacks.json')),
- 'linux': os.path.abspath(os.path.join(os.path.abspath(__file__), '../resources/LinuxFunctionsStacks.json'))
-}
-
-FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX = r"^.*\|(.*)$"
-FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX = r"^~(.*)$"
diff --git a/src/appservice-kube/azext_appservice_kube/_create_util.py b/src/appservice-kube/azext_appservice_kube/_create_util.py
index 740512fd9fa..c4c42e5b3ca 100644
--- a/src/appservice-kube/azext_appservice_kube/_create_util.py
+++ b/src/appservice-kube/azext_appservice_kube/_create_util.py
@@ -3,291 +3,13 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
-import os
-import zipfile
-from knack.log import get_logger
-from azure.mgmt.web.models import SkuDescription
-from azure.cli.core.azclierror import ValidationError, ArgumentUsageError
+from knack.log import get_logger
-from ._constants import (NETCORE_VERSION_DEFAULT, NETCORE_VERSIONS, NODE_VERSION_DEFAULT,
- NODE_VERSIONS, NETCORE_RUNTIME_NAME, NODE_RUNTIME_NAME, DOTNET_RUNTIME_NAME,
- DOTNET_VERSION_DEFAULT, DOTNET_VERSIONS, STATIC_RUNTIME_NAME,
- PYTHON_RUNTIME_NAME, PYTHON_VERSION_DEFAULT, LINUX_SKU_DEFAULT, OS_DEFAULT)
-from ._client_factory import web_client_factory, resource_client_factory
+from ._client_factory import web_client_factory
logger = get_logger(__name__)
-def get_runtime_version_details(file_path, lang_name, is_kube=False):
- version_detected = None
- version_to_create = None
- if lang_name.lower() == NETCORE_RUNTIME_NAME:
- # method returns list in DESC, pick the first
- version_detected = parse_netcore_version(file_path)[0]
- version_to_create = detect_netcore_version_tocreate(version_detected)
- elif lang_name.lower() == DOTNET_RUNTIME_NAME:
- # method returns list in DESC, pick the first
- version_detected = parse_dotnet_version(file_path)
- version_to_create = detect_dotnet_version_tocreate(version_detected)
- if is_kube:
- raise ValidationError("Dotnet runtime is not supported for Kube Environments")
- elif lang_name.lower() == NODE_RUNTIME_NAME:
- if file_path == '':
- version_detected = "-"
- version_to_create = NODE_VERSION_DEFAULT
- else:
- version_detected = parse_node_version(file_path)[0]
- version_to_create = detect_node_version_tocreate(version_detected)
- elif lang_name.lower() == PYTHON_RUNTIME_NAME:
- version_detected = "-"
- version_to_create = PYTHON_VERSION_DEFAULT
- elif lang_name.lower() == STATIC_RUNTIME_NAME:
- version_detected = "-"
- version_to_create = "-"
- return {'detected': version_detected, 'to_create': version_to_create}
-
-
-def zip_contents_from_dir(dirPath, lang, is_kube=None):
- relroot = os.path.abspath(os.path.join(dirPath, os.pardir))
-
- path_and_file = os.path.splitdrive(dirPath)[1]
- file_val = os.path.split(path_and_file)[1]
- zip_file_path = relroot + os.path.sep + file_val + ".zip"
- abs_src = os.path.abspath(dirPath)
- with zipfile.ZipFile("{}".format(zip_file_path), "w", zipfile.ZIP_DEFLATED) as zf:
- for dirname, subdirs, files in os.walk(dirPath):
- # skip node_modules folder for Node apps,
- # since zip_deployment will perform the build operation
- # TODO: Add .deployment support in Kube BuildSVC and remove this check
- if not is_kube:
- if lang.lower() == NODE_RUNTIME_NAME:
- subdirs[:] = [d for d in subdirs if 'node_modules' not in d]
- elif lang.lower() == NETCORE_RUNTIME_NAME:
- subdirs[:] = [d for d in subdirs if d not in ['obj', 'bin']]
- elif lang.lower() == PYTHON_RUNTIME_NAME:
- subdirs[:] = [d for d in subdirs if 'env' not in d] # Ignores dir that contain env
- for filename in files:
- absname = os.path.abspath(os.path.join(dirname, filename))
- arcname = absname[len(abs_src) + 1:]
- zf.write(absname, arcname)
- return zip_file_path
-
-
-def create_resource_group(cmd, rg_name, location):
- from azure.cli.core.profiles import ResourceType, get_sdk
- rcf = resource_client_factory(cmd.cli_ctx)
- resource_group = get_sdk(cmd.cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, 'ResourceGroup', mod='models')
- rg_params = resource_group(location=location)
- return rcf.resource_groups.create_or_update(rg_name, rg_params)
-
-
-def _check_resource_group_exists(cmd, rg_name):
- rcf = resource_client_factory(cmd.cli_ctx)
- return rcf.resource_groups.check_existence(rg_name)
-
-
-def _check_resource_group_supports_os(cmd, rg_name, is_linux):
- # get all appservice plans from RG
- client = web_client_factory(cmd.cli_ctx)
- plans = list(client.app_service_plans.list_by_resource_group(rg_name))
- for item in plans:
- # for Linux if an app with reserved==False exists, ASP doesn't support Linux
- if is_linux and not item.reserved:
- return False
- if not is_linux and item.reserved:
- return False
- return True
-
-
-def get_num_apps_in_asp(cmd, rg_name, asp_name):
- client = web_client_factory(cmd.cli_ctx)
- return len(list(client.app_service_plans.list_web_apps(rg_name, asp_name)))
-
-
-# pylint:disable=unexpected-keyword-arg
-def get_lang_from_content(src_path, html=False):
- # NODE: package.json should exist in the application root dir
- # NETCORE & DOTNET: *.csproj should exist in the application dir
- # NETCORE: netcoreapp2.0
- # DOTNET: v4.5.2
- runtime_details_dict = dict.fromkeys(['language', 'file_loc', 'default_sku'])
- package_json_file = os.path.join(src_path, 'package.json')
- package_python_file = os.path.join(src_path, 'requirements.txt')
- static_html_file = ""
- package_netcore_file = ""
- runtime_details_dict['language'] = ''
- runtime_details_dict['file_loc'] = ''
- runtime_details_dict['default_sku'] = 'F1'
- import fnmatch
- for _dirpath, _dirnames, files in os.walk(src_path):
- for file in files:
- if html and (fnmatch.fnmatch(file, "*.html") or fnmatch.fnmatch(file, "*.htm") or
- fnmatch.fnmatch(file, "*shtml.")):
- static_html_file = os.path.join(src_path, file)
- break
- if fnmatch.fnmatch(file, "*.csproj"):
- package_netcore_file = os.path.join(src_path, file)
- break
-
- if html:
- if static_html_file:
- runtime_details_dict['language'] = STATIC_RUNTIME_NAME
- runtime_details_dict['file_loc'] = static_html_file
- runtime_details_dict['default_sku'] = 'F1'
- else:
- raise ArgumentUsageError("The html flag was passed, but could not find HTML files, "
- "see 'https://go.microsoft.com/fwlink/?linkid=2109470' for more information")
- elif os.path.isfile(package_python_file):
- runtime_details_dict['language'] = PYTHON_RUNTIME_NAME
- runtime_details_dict['file_loc'] = package_python_file
- runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT
- elif os.path.isfile(package_json_file) or os.path.isfile('server.js') or os.path.isfile('index.js'):
- runtime_details_dict['language'] = NODE_RUNTIME_NAME
- runtime_details_dict['file_loc'] = package_json_file if os.path.isfile(package_json_file) else ''
- runtime_details_dict['default_sku'] = LINUX_SKU_DEFAULT
- elif package_netcore_file:
- runtime_lang = detect_dotnet_lang(package_netcore_file)
- runtime_details_dict['language'] = runtime_lang
- runtime_details_dict['file_loc'] = package_netcore_file
- runtime_details_dict['default_sku'] = 'F1'
- else: # TODO: Update the doc when the detection logic gets updated
- raise ValidationError("Could not auto-detect the runtime stack of your app, "
- "see 'https://go.microsoft.com/fwlink/?linkid=2109470' for more information")
- return runtime_details_dict
-
-
-def detect_dotnet_lang(csproj_path):
- import xml.etree.ElementTree as ET
- import re
- parsed_file = ET.parse(csproj_path)
- root = parsed_file.getroot()
- version_lang = ''
- for target_ver in root.iter('TargetFramework'):
- version_lang = re.sub(r'([^a-zA-Z\s]+?)', '', target_ver.text)
- if 'netcore' in version_lang.lower():
- return NETCORE_RUNTIME_NAME
- return DOTNET_RUNTIME_NAME
-
-
-def parse_dotnet_version(file_path):
- version_detected = ['4.7']
- try:
- from xml.dom import minidom
- import re
- xmldoc = minidom.parse(file_path)
- framework_ver = xmldoc.getElementsByTagName('TargetFrameworkVersion')
- target_ver = framework_ver[0].firstChild.data
- non_decimal = re.compile(r'[^\d.]+')
- # reduce the version to '5.7.4' from '5.7'
- if target_ver is not None:
- # remove the string from the beginning of the version value
- c = non_decimal.sub('', target_ver)
- version_detected = c[:3]
- except: # pylint: disable=bare-except
- version_detected = version_detected[0]
- return version_detected
-
-
-def parse_netcore_version(file_path):
- import xml.etree.ElementTree as ET
- import re
- version_detected = ['0.0']
- parsed_file = ET.parse(file_path)
- root = parsed_file.getroot()
- for target_ver in root.iter('TargetFramework'):
- version_detected = re.findall(r"\d+\.\d+", target_ver.text)
- # incase of multiple versions detected, return list in descending order
- version_detected = sorted(version_detected, key=float, reverse=True)
- return version_detected
-
-
-def parse_node_version(file_path):
- # from node experts the node value in package.json can be found here "engines": { "node": ">=10.6.0"}
- import json
- import re
- version_detected = []
- with open(file_path) as data_file:
- data = json.load(data_file)
- for key, value in data.items():
- if key == 'engines' and 'node' in value:
- value_detected = value['node']
- non_decimal = re.compile(r'[^\d.]+')
- # remove the string ~ or > that sometimes exists in version value
- c = non_decimal.sub('', value_detected)
- # reduce the version to '6.0' from '6.0.0'
- if '.' in c: # handle version set as 4 instead of 4.0
- num_array = c.split('.')
- num = num_array[0] + "." + num_array[1]
- else:
- num = c + ".0"
- version_detected.append(num)
- return version_detected or ['0.0']
-
-
-def detect_netcore_version_tocreate(detected_ver):
- if detected_ver in NETCORE_VERSIONS:
- return detected_ver
- return NETCORE_VERSION_DEFAULT
-
-
-def detect_dotnet_version_tocreate(detected_ver):
- min_ver = DOTNET_VERSIONS[0]
- if detected_ver in DOTNET_VERSIONS:
- return detected_ver
- if detected_ver < min_ver:
- return min_ver
- return DOTNET_VERSION_DEFAULT
-
-
-def detect_node_version_tocreate(detected_ver):
- if detected_ver in NODE_VERSIONS:
- return detected_ver
- # get major version & get the closest version from supported list
- major_ver = int(detected_ver.split('.')[0])
- node_ver = NODE_VERSION_DEFAULT
- if major_ver < 4:
- node_ver = NODE_VERSION_DEFAULT
- elif 4 <= major_ver < 6:
- node_ver = '4.5'
- elif 6 <= major_ver < 8:
- node_ver = '6.9'
- elif 8 <= major_ver < 10:
- node_ver = NODE_VERSION_DEFAULT
- elif major_ver >= 10:
- node_ver = '10.14'
- return node_ver
-
-
-def find_key_in_json(json_data, key):
- for k, v in json_data.items():
- if key in k:
- yield v
- elif isinstance(v, dict):
- for id_val in find_key_in_json(v, key):
- yield id_val
-
-
-def set_location(cmd, sku, location):
- client = web_client_factory(cmd.cli_ctx)
- if location is None:
- locs = client.list_geo_regions(sku, True)
- available_locs = []
- for loc in locs:
- available_locs.append(loc.name)
- loc = available_locs[0]
- else:
- loc = location
- return loc.replace(" ", "").lower()
-
-
-# check if the RG value to use already exists and follows the OS requirements or new RG to be created
-def should_create_new_rg(cmd, rg_name, is_linux):
- if (_check_resource_group_exists(cmd, rg_name) and
- _check_resource_group_supports_os(cmd, rg_name, is_linux)):
- return False
- return True
-
-
def get_site_availability(cmd, name):
""" This is used by az webapp up to verify if a site needs to be created or should just be deployed"""
client = web_client_factory(cmd.cli_ctx)
@@ -302,14 +24,6 @@ def get_site_availability(cmd, name):
return availability
-def does_app_already_exist(cmd, name):
- """ This is used by az webapp up to verify if a site needs to be created or should just be deployed"""
- client = web_client_factory(cmd.cli_ctx)
- site_availability = client.check_name_availability(name, 'Microsoft.Web/sites')
- # check availability returns true to name_available == site does not exist
- return site_availability.name_available
-
-
def get_app_details(cmd, name, resource_group):
client = web_client_factory(cmd.cli_ctx)
return client.web_apps.get(resource_group, name)
@@ -322,64 +36,6 @@ def get_app_details(cmd, name, resource_group):
# '''
-def get_rg_to_use(cmd, user, loc, os_name, rg_name=None):
- default_rg = "{}_rg_{}_{}".format(user, os_name, loc.replace(" ", "").lower())
- # check if RG exists & can be used
- if rg_name is not None and _check_resource_group_exists(cmd, rg_name):
- if _check_resource_group_supports_os(cmd, rg_name, os_name.lower() == 'linux'):
- return rg_name
- raise ArgumentUsageError("The ResourceGroup '{}' cannot be used with the os '{}'. "
- "Use a different RG".format(rg_name, os_name))
- if rg_name is None:
- rg_name = default_rg
- return rg_name
-
-
-def get_profile_username():
- from azure.cli.core._profile import Profile
- user = Profile().get_current_account_user()
- user = user.split('@', 1)[0]
- if len(user.split('#', 1)) > 1: # on cloudShell user is in format live.com#user@domain.com
- user = user.split('#', 1)[1]
- return user
-
-
-def get_sku_to_use(src_dir, html=False, sku=None):
- if sku is None:
- lang_details = get_lang_from_content(src_dir, html)
- return lang_details.get("default_sku")
- logger.info("Found sku argument, skipping use default sku")
- return sku
-
-
-def set_language(src_dir, html=False):
- lang_details = get_lang_from_content(src_dir, html)
- return lang_details.get('language')
-
-
-def detect_os_form_src(src_dir, html=False):
- lang_details = get_lang_from_content(src_dir, html)
- language = lang_details.get('language')
- return "Linux" if language is not None and language.lower() == NODE_RUNTIME_NAME \
- or language.lower() == PYTHON_RUNTIME_NAME else OS_DEFAULT
-
-
-def get_plan_to_use(cmd, user, os_name, loc, sku, create_rg, resource_group_name, plan=None):
- _default_asp = "{}_asp_{}_{}_0".format(user, os_name, loc)
- if plan is None: # --plan not provided by user
- # get the plan name to use
- return _determine_if_default_plan_to_use(cmd, _default_asp, resource_group_name, loc, sku, create_rg)
- return plan
-
-
-def get_kube_plan_to_use(cmd, kube_environment, loc, sku, create_rg, resource_group_name, plan=None):
- _default_asp = "{}_asp_{}_{}_0".format(kube_environment, sku, resource_group_name)
- if plan is None: # --plan not provided by user
- # get the plan name to use
- return _determine_if_default_plan_to_use(cmd, _default_asp, resource_group_name, loc, sku, create_rg)
- return plan
-
-
# Portal uses the current_stack property in the app metadata to display the correct stack
# This value should be one of: ['dotnet', 'dotnetcore', 'node', 'php', 'python', 'java']
def get_current_stack_from_runtime(runtime):
@@ -389,40 +45,6 @@ def get_current_stack_from_runtime(runtime):
return language
-# if plan name not provided we need to get a plan name based on the OS, location & SKU
-def _determine_if_default_plan_to_use(cmd, plan_name, resource_group_name, loc, sku, create_rg):
- client = web_client_factory(cmd.cli_ctx)
- if create_rg: # if new RG needs to be created use the default name
- return plan_name
- # get all ASPs in the RG & filter to the ones that contain the plan_name
- _asp_generic = plan_name[:-len(plan_name.split("_")[4])]
- _asp_list = (list(filter(lambda x: _asp_generic in x.name,
- client.app_service_plans.list_by_resource_group(resource_group_name))))
- _num_asp = len(_asp_list)
- if _num_asp:
- # check if we have at least one app that can be used with the combination of loc, sku & os
- selected_asp = next((a for a in _asp_list if isinstance(a.sku, SkuDescription) and
- a.sku.name.lower() == sku.lower() and
- (a.location.replace(" ", "").lower() == loc.lower())), None)
- if selected_asp is not None:
- return selected_asp.name
- # from the sorted data pick the last one & check if a new ASP needs to be created
- # based on SKU or not
- data_sorted = sorted(_asp_list, key=lambda x: x.name)
- _plan_info = data_sorted[_num_asp - 1]
- _asp_num = int(_plan_info.name.split('_')[4]) + 1 # default asp created by CLI can be of type plan_num
- return '{}_{}'.format(_asp_generic, _asp_num)
- return plan_name
-
-
-def should_create_new_app(cmd, rg_name, app_name): # this is currently referenced by an extension command
- client = web_client_factory(cmd.cli_ctx)
- for item in list(client.web_apps.list_by_resource_group(rg_name)):
- if item.name.lower() == app_name.lower():
- return False
- return True
-
-
def generate_default_app_service_plan_name(webapp_name):
import uuid
random_uuid = str(uuid.uuid4().hex)
diff --git a/src/appservice-kube/azext_appservice_kube/_params.py b/src/appservice-kube/azext_appservice_kube/_params.py
index 9b8184d33df..a0ab334f57f 100644
--- a/src/appservice-kube/azext_appservice_kube/_params.py
+++ b/src/appservice-kube/azext_appservice_kube/_params.py
@@ -10,9 +10,9 @@
from azure.cli.core.commands.parameters import (resource_group_name_type, get_location_type,
get_resource_name_completion_list,
get_three_state_flag, get_enum_type, tags_type)
+from azure.cli.command_modules.appservice._constants import FUNCTIONS_VERSIONS
-from ._constants import (FUNCTIONS_VERSIONS, FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS,
- LINUX_RUNTIMES, WINDOWS_RUNTIMES, MULTI_CONTAINER_TYPES, OS_TYPES)
+from ._constants import MULTI_CONTAINER_TYPES, OS_TYPES
from ._validators import validate_asp_create, validate_timeout_value
@@ -28,20 +28,6 @@ def load_arguments(self, _):
self.get_models('K8SENetworkPlugin')
- # combine all runtime versions for all functions versions
- functionapp_runtime_to_version = {}
- for functions_version in FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS.values():
- for runtime, val in functions_version.items():
- # dotnet version is not configurable, so leave out of help menu
- if runtime != 'dotnet':
- functionapp_runtime_to_version[runtime] = functionapp_runtime_to_version.get(runtime, set()).union(val)
-
- functionapp_runtime_to_version_texts = []
- 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) + ']')
-
with self.argument_context('webapp') as c:
c.ignore('app_instance')
c.argument('resource_group_name', arg_type=resource_group_name_type)
@@ -98,10 +84,9 @@ 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))
- 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))
+ c.argument('functions_version', help='The functions app version. Use "az functionapp list-runtimes" to check compatibility with runtimes and runtime versions', arg_type=get_enum_type(FUNCTIONS_VERSIONS))
+ c.argument('runtime', help='The functions runtime stack. Use "az functionapp list-runtimes" to check supported runtimes and versions')
+ c.argument('runtime_version', help='The version of the functions runtime stack. Use "az functionapp list-runtimes" to check supported runtimes and versions')
c.argument('os_type', arg_type=get_enum_type(OS_TYPES), help="Set the OS type for the app to be created.")
c.argument('app_insights_key', help="Instrumentation key of App Insights to be added.")
c.argument('app_insights', help="Name of the existing App Insights project to be added to the Function app. Must be in the same resource group.")
diff --git a/src/appservice-kube/azext_appservice_kube/azext_metadata.json b/src/appservice-kube/azext_appservice_kube/azext_metadata.json
index 4cc78b7edd0..76cc1bebeb0 100644
--- a/src/appservice-kube/azext_appservice_kube/azext_metadata.json
+++ b/src/appservice-kube/azext_appservice_kube/azext_metadata.json
@@ -1,5 +1,4 @@
{
"azext.isPreview": true,
- "azext.minCliCoreVersion": "2.33.0",
- "azext.maxCliCoreVersion": "2.33.1"
+ "azext.minCliCoreVersion": "2.34.0"
}
diff --git a/src/appservice-kube/azext_appservice_kube/custom.py b/src/appservice-kube/azext_appservice_kube/custom.py
index b47aaff0001..9a950bc4efd 100644
--- a/src/appservice-kube/azext_appservice_kube/custom.py
+++ b/src/appservice-kube/azext_appservice_kube/custom.py
@@ -12,7 +12,7 @@
from knack.util import CLIError
from knack.log import get_logger
-from azure.cli.core.util import send_raw_request, sdk_no_wait, get_json_object, get_file_json
+from azure.cli.core.util import send_raw_request, sdk_no_wait, get_json_object
from azure.cli.core.commands.client_factory import get_subscription_id
from azure.cli.command_modules.appservice.custom import (
update_container_settings,
@@ -37,6 +37,7 @@
_validate_and_get_connection_string,
_get_linux_multicontainer_encoded_config_from_file,
_StackRuntimeHelper,
+ _FunctionAppStackRuntimeHelper,
upload_zip_to_storage,
is_plan_consumption,
_configure_default_logging,
@@ -46,7 +47,7 @@
list_hostnames,
_convert_camel_to_snake_case,
_get_content_share_name)
-from azure.cli.command_modules.appservice._constants import FUNCTIONS_STACKS_API_KEYS, FUNCTIONS_NO_V2_REGIONS
+from azure.cli.command_modules.appservice._constants import LINUX_OS_NAME, FUNCTIONS_NO_V2_REGIONS
from azure.cli.command_modules.appservice.utils import retryable_method
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands import LongRunningOperation
@@ -57,12 +58,8 @@
from msrestazure.tools import is_valid_resource_id, parse_resource_id
-from ._constants import (FUNCTIONS_VERSION_TO_DEFAULT_RUNTIME_VERSION, FUNCTIONS_VERSION_TO_DEFAULT_NODE_VERSION,
- FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS, NODE_EXACT_VERSION_DEFAULT,
- DOTNET_RUNTIME_VERSION_TO_DOTNET_LINUX_FX_VERSION, KUBE_DEFAULT_SKU,
- KUBE_ASP_KIND, KUBE_APP_KIND, KUBE_FUNCTION_APP_KIND, KUBE_FUNCTION_CONTAINER_APP_KIND,
- KUBE_CONTAINER_APP_KIND, LINUX_RUNTIMES, WINDOWS_RUNTIMES, FUNCTIONS_STACKS_API_JSON_PATHS,
- FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX, FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX)
+from ._constants import (KUBE_DEFAULT_SKU, KUBE_ASP_KIND, KUBE_APP_KIND, KUBE_FUNCTION_APP_KIND,
+ KUBE_FUNCTION_CONTAINER_APP_KIND, KUBE_CONTAINER_APP_KIND)
from ._utils import (_normalize_sku, get_sku_name, _generic_site_operation,
_get_location_from_resource_group, _validate_asp_sku)
@@ -755,7 +752,6 @@ def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custo
_validate_asp_sku(app_service_environment=None, custom_location=custom_location, sku=plan_info.sku.name)
is_linux = plan_info.reserved
- node_default_version = NODE_EXACT_VERSION_DEFAULT
location = plan_info.location
if isinstance(plan_info.sku, SkuDescription) and plan_info.sku.name.upper() not in ['F1', 'FREE', 'SHARED', 'D1',
@@ -799,7 +795,7 @@ def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custo
site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_PASSWORD',
value=docker_registry_server_password))
- helper = _StackRuntimeHelper(cmd, client, linux=(is_linux or is_kube))
+ helper = _StackRuntimeHelper(cmd, linux=bool(is_linux or is_kube), windows=not bool(is_linux or is_kube))
if runtime:
runtime = helper.remove_delimiters(runtime)
@@ -814,11 +810,11 @@ def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custo
if runtime:
site_config.linux_fx_version = runtime
- match = helper.resolve(runtime)
+ match = helper.resolve(runtime, linux=True)
if not match:
raise CLIError("Linux Runtime '{}' is not supported."
"Please invoke 'list-runtimes' to cross check".format(runtime))
- match['setter'](cmd=cmd, stack=match, site_config=site_config)
+ helper.get_site_config_setter(match, linux=True)(cmd=cmd, stack=match, site_config=site_config)
elif deployment_container_image_name:
site_config.linux_fx_version = _format_fx_version(deployment_container_image_name)
if name_validation.name_available:
@@ -845,17 +841,18 @@ def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custo
raise CLIError("usage error: --startup-file or --deployment-container-image-name or "
"--multicontainer-config-type and --multicontainer-config-file is "
"only appliable on linux webapp")
- match = helper.resolve(runtime)
+ match = helper.resolve(runtime, linux=False)
if not match:
raise CLIError("Windows runtime '{}' is not supported. "
"Please invoke 'az webapp list-runtimes' to cross check".format(runtime))
- match['setter'](cmd=cmd, stack=match, site_config=site_config)
+ helper.get_site_config_setter(match, linux=is_linux)(cmd=cmd, stack=match, site_config=site_config)
# portal uses the current_stack propety in metadata to display stack for windows apps
current_stack = get_current_stack_from_runtime(runtime)
else: # windows webapp without runtime specified
if name_validation.name_available: # If creating new webapp
+ node_default_version = helper.get_default_version("node", is_linux, get_windows_config_version=True)
site_config.app_settings.append(NameValuePair(name="WEBSITE_NODE_DEFAULT_VERSION",
value=node_default_version))
@@ -941,6 +938,7 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
logger.warning("No functions version specified so defaulting to 3. In the future, specifying a version will "
"be required. To create a 3.x function you would pass in the flag `--functions-version 3`")
functions_version = '3'
+
if deployment_source_url and deployment_local_git:
raise MutuallyExclusiveArgumentError('usage error: --deployment-source-url | --deployment-local-git')
if not plan and not consumption_plan_location and not custom_location:
@@ -956,11 +954,12 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
disable_app_insights = (disable_app_insights == "true")
custom_location = _get_custom_location_id(cmd, custom_location, resource_group_name)
+
site_config = SiteConfig(app_settings=[])
client = web_client_factory(cmd.cli_ctx)
functionapp_def = Site(location=None, site_config=site_config, tags=tags)
- KEYS = FUNCTIONS_STACKS_API_KEYS()
+
plan_info = None
if runtime is not None:
runtime = runtime.lower()
@@ -973,7 +972,7 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
functionapp_def.location = consumption_plan_location
functionapp_def.kind = 'functionapp'
# if os_type is None, the os type is windows
- is_linux = os_type and os_type.lower() == 'linux'
+ is_linux = bool(os_type and os_type.lower() == LINUX_OS_NAME)
else: # apps with SKU based plan
_should_create_new_plan = _should_create_new_appservice_plan_for_k8se(cmd,
@@ -1001,20 +1000,12 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
plan_info = client.app_service_plans.get(resource_group_name, plan)
if not plan_info:
raise ResourceNotFoundError("The plan '{}' doesn't exist".format(plan))
+
location = plan_info.location
- is_linux = plan_info.reserved
+ is_linux = bool(plan_info.reserved)
functionapp_def.server_farm_id = plan
functionapp_def.location = location
- is_kube = _is_function_kube(custom_location, plan_info, SkuDescription)
-
- if is_kube:
- if min_worker_count is not None:
- site_config.number_of_workers = min_worker_count
-
- if max_worker_count is not None:
- site_config.app_settings.append(NameValuePair(name='K8SE_APP_MAX_INSTANCE_COUNT', value=max_worker_count))
-
if functions_version == '2' and functionapp_def.location in FUNCTIONS_NO_V2_REGIONS:
raise ValidationError("2.x functions are not supported in this region. To create a 3.x function, "
"pass in the flag '--functions-version 3'")
@@ -1023,60 +1014,24 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
raise ArgumentUsageError(
"usage error: --runtime RUNTIME required for linux functions apps without custom image.")
- runtime_stacks_json = _load_runtime_stacks_json_functionapp(is_linux)
-
if runtime is None and runtime_version is not None:
raise ArgumentUsageError('Must specify --runtime to use --runtime-version')
- # get the matching runtime stack object
- runtime_json = _get_matching_runtime_json_functionapp(runtime_stacks_json, runtime if runtime else 'dotnet')
- if not runtime_json:
- # no matching runtime for os
- os_string = "linux" if is_linux else "windows"
- supported_runtimes = list(map(lambda x: x[KEYS.NAME], runtime_stacks_json))
- raise ValidationError("usage error: Currently supported runtimes (--runtime) in {} function apps are: {}."
- .format(os_string, ', '.join(supported_runtimes)))
-
- runtime_version_json = _get_matching_runtime_version_json_functionapp(runtime_json,
- functions_version,
- runtime_version,
- is_linux)
-
- if not runtime_version_json:
- supported_runtime_versions = list(map(lambda x: x[KEYS.DISPLAY_VERSION],
- _get_supported_runtime_versions_functionapp(runtime_json,
- functions_version)))
- if runtime_version:
- if runtime == 'dotnet':
- raise ArgumentUsageError('--runtime-version is not supported for --runtime dotnet. Dotnet version is '
- 'determined by --functions-version. Dotnet version {} '
- 'is not supported by Functions version {}.'
- .format(runtime_version, functions_version))
- raise ArgumentUsageError('--runtime-version {} is not supported for the selected --runtime {} and '
- '--functions-version {}. Supported versions are: {}.'
- .format(runtime_version,
- runtime,
- functions_version,
- ', '.join(supported_runtime_versions)))
-
- # if runtime_version was not specified, then that runtime is not supported for that functions version
- raise ArgumentUsageError('no supported --runtime-version found for the selected --runtime {} and '
- '--functions-version {}'
- .format(runtime, functions_version))
-
- if runtime == 'dotnet':
- logger.warning('--runtime-version is not supported for --runtime dotnet. Dotnet version is determined by '
- '--functions-version. Dotnet version will be %s for this function app.',
- runtime_version_json[KEYS.DISPLAY_VERSION])
-
- if runtime_version_json[KEYS.IS_DEPRECATED]:
- logger.warning('%s version %s has been deprecated. In the future, this version will be unavailable. '
- 'Please update your command to use a more recent version. For a list of supported '
- '--runtime-versions, run \"az functionapp create -h\"',
- runtime_json[KEYS.PROPERTIES][KEYS.DISPLAY], runtime_version_json[KEYS.DISPLAY_VERSION])
-
- site_config_json = runtime_version_json[KEYS.SITE_CONFIG_DICT]
- app_settings_json = runtime_version_json[KEYS.APP_SETTINGS_DICT]
+ is_kube = _is_function_kube(custom_location, plan_info, SkuDescription)
+
+ runtime_helper = _FunctionAppStackRuntimeHelper(cmd, linux=is_linux, windows=(not is_linux))
+ matched_runtime = runtime_helper.resolve("dotnet" if not runtime else runtime,
+ runtime_version, functions_version, is_linux)
+
+ if is_kube:
+ if min_worker_count is not None:
+ site_config.number_of_workers = min_worker_count
+
+ if max_worker_count is not None:
+ site_config.app_settings.append(NameValuePair(name='K8SE_APP_MAX_INSTANCE_COUNT', value=max_worker_count))
+
+ site_config_dict = matched_runtime.site_config_dict
+ app_settings_dict = matched_runtime.app_settings_dict
con_string = _validate_and_get_connection_string(cmd.cli_ctx, resource_group_name, storage_account)
@@ -1130,11 +1085,11 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
site_config.linux_fx_version = _format_fx_version(deployment_container_image_name)
# clear all runtime specific configs and settings
- site_config_json = {KEYS.USE_32_BIT_WORKER_PROC: False}
- app_settings_json = {}
+ site_config_dict.use32_bit_worker_process = False
+ app_settings_dict = {}
# ensure that app insights is created if not disabled
- runtime_version_json[KEYS.APPLICATION_INSIGHTS] = True
+ matched_runtime.app_insights = True
else:
site_config.app_settings.append(NameValuePair(name='WEBSITES_ENABLE_APP_SERVICE_STORAGE',
value='true'))
@@ -1142,7 +1097,7 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
functionapp_def.kind = 'functionapp'
# set site configs
- for prop, value in site_config_json.items():
+ for prop, value in site_config_dict.as_dict().items():
snake_case_prop = _convert_camel_to_snake_case(prop)
setattr(site_config, snake_case_prop, value)
@@ -1151,7 +1106,7 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
site_config.linux_fx_version = ''
# adding app settings
- for app_setting, value in app_settings_json.items():
+ for app_setting, value in app_settings_dict.items():
site_config.app_settings.append(NameValuePair(name=app_setting, value=value))
site_config.app_settings.append(NameValuePair(name='FUNCTIONS_EXTENSION_VERSION',
@@ -1177,10 +1132,10 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
instrumentation_key = get_app_insights_key(cmd.cli_ctx, resource_group_name, app_insights)
site_config.app_settings.append(NameValuePair(name='APPINSIGHTS_INSTRUMENTATIONKEY',
value=instrumentation_key))
- elif disable_app_insights or not runtime_version_json[KEYS.APPLICATION_INSIGHTS]:
+ elif disable_app_insights or not matched_runtime.app_insights:
# set up dashboard if no app insights
site_config.app_settings.append(NameValuePair(name='AzureWebJobsDashboard', value=con_string))
- elif not disable_app_insights and runtime_version_json[KEYS.APPLICATION_INSIGHTS]:
+ elif not disable_app_insights and matched_runtime.app_insights:
create_app_insights = True
poller = client.web_apps.begin_create_or_update(resource_group_name, name, functionapp_def)
@@ -1401,38 +1356,12 @@ def _resolve_kube_environment_id(cli_ctx, kube_environment, resource_group_name)
name=kube_environment)
-def _get_linux_fx_functionapp(functions_version, runtime, runtime_version):
- if runtime_version is None:
- runtime_version = FUNCTIONS_VERSION_TO_DEFAULT_RUNTIME_VERSION[functions_version][runtime]
- if runtime == 'dotnet':
- runtime_version = DOTNET_RUNTIME_VERSION_TO_DOTNET_LINUX_FX_VERSION[runtime_version]
- else:
- runtime = runtime.upper()
- return '{}|{}'.format(runtime, runtime_version)
-
-
def _get_linux_fx_kube_functionapp(runtime, runtime_version):
if runtime.upper() == "DOTNET":
runtime = "DOTNETCORE"
return '{}|{}'.format(runtime.upper(), runtime_version)
-def _get_website_node_version_functionapp(functions_version, runtime, runtime_version):
- if runtime is None or runtime != 'node':
- return FUNCTIONS_VERSION_TO_DEFAULT_NODE_VERSION[functions_version]
- if runtime_version is not None:
- return '~{}'.format(runtime_version)
- return FUNCTIONS_VERSION_TO_DEFAULT_NODE_VERSION[functions_version]
-
-
-def _get_java_version_functionapp(functions_version, runtime_version):
- if runtime_version is None:
- runtime_version = FUNCTIONS_VERSION_TO_DEFAULT_RUNTIME_VERSION[functions_version]['java']
- if runtime_version == '8':
- return '1.8'
- return runtime_version
-
-
def _set_remote_or_local_git(cmd, webapp, resource_group_name, name, deployment_source_url=None,
deployment_source_branch='master', deployment_local_git=None):
if deployment_source_url:
@@ -1450,17 +1379,6 @@ def _set_remote_or_local_git(cmd, webapp, resource_group_name, name, deployment_
setattr(webapp, 'deploymentLocalGitUrl', local_git_info['url'])
-def _add_fx_version(cmd, resource_group_name, name, custom_image_name, slot=None):
- fx_version = _format_fx_version(custom_image_name)
- web_app = get_webapp(cmd, resource_group_name, name, slot)
- if not web_app:
- raise CLIError("'{}' app doesn't exist in resource group {}".format(name, resource_group_name))
- linux_fx = fx_version if (web_app.reserved or not web_app.is_xenon) else None
- windows_fx = fx_version if web_app.is_xenon else None
- return update_site_configs(cmd, resource_group_name, name,
- linux_fx_version=linux_fx, windows_fx_version=windows_fx, slot=slot)
-
-
@retryable_method(3, 5)
def _get_app_settings_from_scm(cmd, resource_group_name, name, slot=None):
scm_url = _get_scm_url(cmd, resource_group_name, name, slot)
@@ -1596,8 +1514,8 @@ def enable_zip_deploy_functionapp(cmd, resource_group_name, name, src, build_rem
client = web_client_factory(cmd.cli_ctx)
app = client.web_apps.get(resource_group_name, name)
if app is None:
- raise CLIError('The function app \'{}\' was not found in resource group \'{}\'. '
- 'Please make sure these values are correct.'.format(name, resource_group_name))
+ raise ResourceNotFoundError('The function app \'{}\' was not found in resource group \'{}\'. '
+ 'Please make sure these values are correct.'.format(name, resource_group_name))
parse_plan_id = parse_resource_id(app.server_farm_id)
plan_info = None
retry_delay = 10 # seconds
@@ -1611,7 +1529,7 @@ def enable_zip_deploy_functionapp(cmd, resource_group_name, name, src, build_rem
time.sleep(retry_delay)
if build_remote and not app.reserved:
- raise CLIError('Remote build is only available on Linux function apps')
+ raise ValidationError('Remote build is only available on Linux function apps')
is_consumption = is_plan_consumption(cmd, plan_info)
if (not build_remote) and is_consumption and app.reserved:
@@ -1835,69 +1753,3 @@ def unbind_ssl_cert(cmd, resource_group_name, name, certificate_thumbprint, slot
SslState = cmd.get_models('SslState')
return _update_ssl_binding(cmd, resource_group_name, name,
certificate_thumbprint, SslState.disabled, slot)
-
-
-def _load_runtime_stacks_json_functionapp(is_linux):
- KEYS = FUNCTIONS_STACKS_API_KEYS()
- if is_linux:
- return get_file_json(FUNCTIONS_STACKS_API_JSON_PATHS['linux'])[KEYS.VALUE]
- return get_file_json(FUNCTIONS_STACKS_API_JSON_PATHS['windows'])[KEYS.VALUE]
-
-
-def _get_matching_runtime_json_functionapp(stacks_json, runtime):
- KEYS = FUNCTIONS_STACKS_API_KEYS()
- matching_runtime_json = list(filter(lambda x: x[KEYS.NAME] == runtime, stacks_json))
- if matching_runtime_json:
- return matching_runtime_json[0]
- return None
-
-
-def _get_matching_runtime_version_json_functionapp(runtime_json, functions_version, runtime_version, is_linux):
- KEYS = FUNCTIONS_STACKS_API_KEYS()
- extension_version = _get_extension_version_functionapp(functions_version)
- if runtime_version:
- for runtime_version_json in runtime_json[KEYS.PROPERTIES][KEYS.MAJOR_VERSIONS]:
- if (runtime_version_json[KEYS.DISPLAY_VERSION] == runtime_version and
- extension_version in runtime_version_json[KEYS.SUPPORTED_EXTENSION_VERSIONS]):
- return runtime_version_json
- return None
-
- # find the matching default runtime version
- supported_versions_list = _get_supported_runtime_versions_functionapp(runtime_json, functions_version)
- default_version_json = {}
- default_version = 0.0
- for current_runtime_version_json in supported_versions_list:
- if current_runtime_version_json[KEYS.IS_DEFAULT]:
- current_version = _get_runtime_version_functionapp(current_runtime_version_json[KEYS.RUNTIME_VERSION],
- is_linux)
- if not default_version_json or default_version < current_version:
- default_version_json = current_runtime_version_json
- default_version = current_version
- return default_version_json
-
-
-def _get_supported_runtime_versions_functionapp(runtime_json, functions_version):
- KEYS = FUNCTIONS_STACKS_API_KEYS()
- extension_version = _get_extension_version_functionapp(functions_version)
- supported_versions_list = []
-
- for runtime_version_json in runtime_json[KEYS.PROPERTIES][KEYS.MAJOR_VERSIONS]:
- if extension_version in runtime_version_json[KEYS.SUPPORTED_EXTENSION_VERSIONS]:
- supported_versions_list.append(runtime_version_json)
- return supported_versions_list
-
-
-def _get_runtime_version_functionapp(version_string, is_linux):
- import re
- windows_match = re.fullmatch(FUNCTIONS_WINDOWS_RUNTIME_VERSION_REGEX, version_string)
- if windows_match:
- return float(windows_match.group(1))
-
- linux_match = re.fullmatch(FUNCTIONS_LINUX_RUNTIME_VERSION_REGEX, version_string)
- if linux_match:
- return float(linux_match.group(1))
-
- try:
- return float(version_string)
- except ValueError:
- return 0
diff --git a/src/appservice-kube/setup.py b/src/appservice-kube/setup.py
index 71bd2f2124b..9ef132e72cc 100644
--- a/src/appservice-kube/setup.py
+++ b/src/appservice-kube/setup.py
@@ -16,7 +16,7 @@
# TODO: Confirm this is the right version number you want and it matches your
# HISTORY.rst entry.
-VERSION = '0.1.3'
+VERSION = '0.1.4'
# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers