text: # --------------------------------------------------------------------------------------------
  # Copyright (c) Microsoft Corporation. All rights reserved.
  # Licensed under the MIT License. See License.txt in the project root for license information.
  # --------------------------------------------------------------------------------------------
  
  import time
  
  from binascii import hexlify
  from os import urandom
  import json
  
  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
  from azure.cli.core.commands.client_factory import get_subscription_id
  from azure.cli.command_modules.appservice.custom import (
      update_container_settings,
      _rename_server_farm_props,
      get_site_configs,
      get_webapp,
      _get_site_credential,
      _format_fx_version,
      _get_extension_version_functionapp,
      _validate_app_service_environment_id,
      _get_location_from_webapp,
      validate_and_convert_to_int,
      validate_range_of_int_flag,
      get_app_settings,
      get_app_insights_key,
      _update_webapp_current_stack_property_if_needed,
      validate_container_app_create_options,
      parse_docker_image_name,
      list_consumption_locations,
      is_plan_elastic_premium,
      enable_local_git,
      _validate_and_get_connection_string,
      _get_linux_multicontainer_encoded_config_from_file,
      _StackRuntimeHelper,
      upload_zip_to_storage,
      is_plan_consumption,
      _configure_default_logging,
      assign_identity,
      delete_app_settings,
      update_app_settings,
      list_hostnames)
  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
  from azure.mgmt.applicationinsights import ApplicationInsightsManagementClient
  from azure.cli.core.util import get_az_user_agent
  from azure.cli.core.azclierror import (ResourceNotFoundError, RequiredArgumentMissingError, ValidationError,
                                         ArgumentUsageError, MutuallyExclusiveArgumentError)
  
  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)
  
  from ._utils import (_normalize_sku, get_sku_name, _generic_site_operation,
                       _get_location_from_resource_group, _validate_asp_sku)
  from ._create_util import (get_app_details, get_site_availability, get_current_stack_from_runtime,
                             generate_default_app_service_plan_name)
  from ._client_factory import web_client_factory, ex_handler_factory, customlocation_client_factory
  
  
  logger = get_logger(__name__)
  
  # pylint: disable=too-many-locals,too-many-lines
  
  
  # TODO remove and replace with calls to KubeEnvironmentsOperations once the SDK gets updated
  class KubeEnvironmentClient():
      @classmethod
      def create(cls, cmd, resource_group_name, name, kube_environment_envelope):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/kubeEnvironments/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(kube_environment_envelope))
          return r.json()
  
      @classmethod
      def update(cls, cmd, name, resource_group_name, kube_environment_envelope):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/kubeEnvironments/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "PATCH", request_url, body=json.dumps(kube_environment_envelope))
          return r.json()
  
      @classmethod
      def show(cls, cmd, resource_group_name, name):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/kubeEnvironments/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "GET", request_url)
          return r.json()
  
      @classmethod
      def delete(cls, cmd, resource_group_name, name):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/kubeEnvironments/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          send_raw_request(cmd.cli_ctx, "DELETE", request_url)  # API doesn't return JSON for some reason
  
      @classmethod
      def list_by_subscription(cls, cmd, formatter=lambda x: x):
          kube_list = []
  
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          request_url = "{}/subscriptions/{}/providers/Microsoft.Web/kubeEnvironments?api-version={}".format(
              management_hostname.strip('/'),
              sub_id,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "GET", request_url)
          j = r.json()
          for kube in j["value"]:
              formatted = formatter(kube)
              kube_list.append(formatted)
  
          while j.get("nextLink") is not None:
              request_url = j["nextLink"]
              r = send_raw_request(cmd.cli_ctx, "GET", request_url)
              j = r.json()
              for kube in j["value"]:
                  formatted = formatter(kube)
                  kube_list.append(formatted)
  
          return kube_list
  
      @classmethod
      def list_by_resource_group(cls, cmd, resource_group_name, formatter=lambda x: x):
          kube_list = []
  
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/kubeEnvironments?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "GET", request_url)
          j = r.json()
          for kube in j["value"]:
              formatted = formatter(kube)
              kube_list.append(formatted)
  
          while j.get("nextLink") is not None:
              request_url = j["nextLink"]
              r = send_raw_request(cmd.cli_ctx, "GET", request_url)
              j = r.json()
              for kube in j["value"]:
                  formatted = formatter(kube)
                  kube_list.append(formatted)
  
          return kube_list
  
  
  class AppServiceClient():
      @classmethod
      def create(cls, cmd, name, resource_group_name, appservice_json):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
  
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/serverfarms/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(appservice_json))
          return r.json()
  
      @classmethod
      def update(cls, cmd, name, resource_group_name, appservice_json):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
  
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/serverfarms/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(appservice_json))
          return r.json()
  
      @classmethod
      def show(cls, cmd, name, resource_group_name):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
  
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/serverfarms/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "GET", request_url)
          return r.json()
  
  
  class WebAppClient:
      @classmethod
      def create(cls, cmd, name, resource_group_name, webapp_json):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
  
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/sites/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              name,
              api_version)
  
          r = send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(webapp_json))
          return r.json()
  
      @classmethod
      def restart(cls, cmd, resource_group_name, name, slot=None):
          management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
          api_version = "2020-12-01"
          sub_id = get_subscription_id(cmd.cli_ctx)
  
          if slot is not None:
              url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/"\
                        "Microsoft.Web/sites/{}/slots/{}/restart?api-version={}"
              request_url = url_fmt.format(
                  management_hostname.strip('/'),
                  sub_id,
                  resource_group_name,
                  name,
                  slot,
                  api_version)
          else:
              url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/sites/{}/restart?api-version={}"
              request_url = url_fmt.format(
                  management_hostname.strip('/'),
                  sub_id,
                  resource_group_name,
                  name,
                  api_version)
  
          send_raw_request(cmd.cli_ctx, "POST", request_url)
  
  
  # rectify the format of the kube env json returned from API to comply with older version of `az appservice kube show`
  def format_kube_environment_json(kube_info_raw):
      kube_info = kube_info_raw["properties"]
      if kube_info.get("aksResourceID"):
          kube_info["aksResourceId"] = kube_info["aksResourceID"]
          del kube_info["aksResourceID"]
  
      other_properties = ['id', 'kind', 'kubeEnvironmentType', 'location', 'name',
                          'resourceGroup', 'tags', 'type', 'extendedLocation']
      for k in other_properties:
          kube_info[k] = kube_info_raw.get(k)
  
      return kube_info
  
  
  def show_kube_environments(cmd, name, resource_group_name):
      return format_kube_environment_json(KubeEnvironmentClient.show(cmd=cmd,
                                                                     name=name, resource_group_name=resource_group_name))
  
  
  def delete_kube_environment(cmd, name, resource_group_name):
      # Raises an exception if the kube environment doesn't exist
      KubeEnvironmentClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
  
      return KubeEnvironmentClient.delete(cmd=cmd, name=name, resource_group_name=resource_group_name)
  
  
  def create_kube_environment(cmd, name, resource_group_name, custom_location, static_ip=None, location=None,
                              tags=None, no_wait=False):
      # pylint: disable=broad-except,no-member,protected-access
  
      custom_location_client = customlocation_client_factory(cmd.cli_ctx)
      custom_location_object = None
  
      if is_valid_resource_id(custom_location):
          parsed_custom_location = parse_resource_id(custom_location)
          if parsed_custom_location['resource_type'].lower() != 'customlocations':
              raise CLIError('Invalid custom location')
          custom_location_object = custom_location_client.custom_locations.get(
              parsed_custom_location['resource_group'],
              parsed_custom_location['name'])
      else:
          custom_location_object = custom_location_client.custom_locations.get(resource_group_name, custom_location)
          custom_location = custom_location_object.id
  
      if not location:
          location = custom_location_object.location
  
      front_end_configuration = {"kind": "LoadBalancer"}
  
      extended_location = {"customLocation": custom_location}
  
      arc_configuration = {
          "artifactsStorageType": "NetworkFileSystem",
          "artifactStorageClassName": "default",
          "frontEndServiceConfiguration": front_end_configuration
      }
  
      kube_environment = {
          "kind": None,
          "location": location,
          "tags": tags,
          "properties": {
              "extendedLocation": extended_location,
              "staticIp": static_ip,
              "arcConfiguration": arc_configuration
          }
      }
  
      try:
          return sdk_no_wait(no_wait, KubeEnvironmentClient.create,
                             cmd=cmd, resource_group_name=resource_group_name,
                             name=name, kube_environment_envelope=kube_environment)
      except Exception as e:
          try:
              msg = json.loads(e.response._content)['Message']
          except Exception as e2:
              raise e from e2
      raise ValidationError(msg)
  
  
  def list_kube_environments(cmd, resource_group_name=None):
      if resource_group_name is None:
          return KubeEnvironmentClient.list_by_subscription(cmd, formatter=format_kube_environment_json)
      return KubeEnvironmentClient.list_by_resource_group(cmd,
                                                          resource_group_name,
                                                          formatter=format_kube_environment_json)
  
  
  # TODO should be able to update staticIp and tags -- remove exception once API fixed
  def update_kube_environment(cmd, name, resource_group_name, custom_location=None, static_ip=None,
                              tags=None, no_wait=False):
      raise CLIError("Update is not yet supported for Kubernetes Environments.")
  
      # Raises an exception if the kube environment doesn't exist
      # KubeEnvironmentClient.show(cmd=cmd, resource_group_name=resource_group_name, name=name)
  
      # front_end_configuration = {"kind": "LoadBalancer"}
  
      # extended_location = {"customLocation": custom_location}
  
      # arc_configuration = {
      #     "artifactsStorageType": "NetworkFileSystem",
      #     "artifactStorageClassName": "default",
      #     "frontEndServiceConfiguration": front_end_configuration
      # }
  
      # kube_environment = {
      #     "kind": None,
      #     "location": location,
      #     "properties": {
      #         "extendedLocation": extended_location,
      #         "staticIp": static_ip,
      #         "arcConfiguration": arc_configuration
      #     }
      # }
  
      # if tags is not None:
      #     kube_environment["tags"] = tags
  
      # return sdk_no_wait(no_wait, KubeEnvironmentClient.update, cmd=cmd, resource_group_name=resource_group_name,
      #                    name=name, kube_environment_envelope=kube_environment)
  
  
  def list_app_service_plans(cmd, resource_group_name=None):
      client = web_client_factory(cmd.cli_ctx)
      if resource_group_name is None:
          plans = list(client.app_service_plans.list())
      else:
          plans = list(client.app_service_plans.list_by_resource_group(resource_group_name))
      for plan in plans:
          # prune a few useless fields
          del plan.geo_region
          del plan.subscription
      return plans
  
  
  def create_app_service_plan(cmd, resource_group_name, name, is_linux, hyper_v, per_site_scaling=False,
                              custom_location=None,
                              app_service_environment=None, sku=None,
                              number_of_workers=None, location=None, tags=None, no_wait=False):
      if not sku:
          sku = 'B1' if not custom_location else KUBE_DEFAULT_SKU
  
      if custom_location:
          if not per_site_scaling:
              raise ArgumentUsageError('Per Site Scaling must be true when using Custom Location. '
                                       'Please re-run with --per-site-scaling flag')
          if app_service_environment:
              raise ArgumentUsageError('App Service Environment is not supported with using Custom Location')
          if hyper_v:
              raise ArgumentUsageError('Hyper V is not supported with using Custom Location')
          if not is_linux:
              raise ArgumentUsageError('Only Linux is supported with using Custom Location. '
                                       'Please re-run with --is-linux flag.')
  
      return create_app_service_plan_inner(cmd, resource_group_name, name, is_linux, hyper_v, per_site_scaling,
                                           custom_location, app_service_environment, sku, number_of_workers, location,
                                           tags, no_wait)
  
  
  def get_vm_sizes(cli_ctx, location):
      from ._client_factory import cf_compute_service
  
      return cf_compute_service(cli_ctx).virtual_machine_sizes.list(location)
  
  
  def _get_kube_env_from_custom_location(cmd, custom_location, resource_group):
      kube_environment_id = ""
      custom_location_name = custom_location
  
      if is_valid_resource_id(custom_location):
          parsed_custom_location = parse_resource_id(custom_location)
          custom_location_name = parsed_custom_location.get("name")
          resource_group = parsed_custom_location.get("resource_group")
  
      kube_envs = KubeEnvironmentClient.list_by_subscription(cmd=cmd)
  
      for kube in kube_envs:
          parsed_custom_location_2 = None
  
          if kube.get("properties") and kube["properties"].get('extendedLocation'):
              parsed_custom_location_2 = parse_resource_id(kube["properties"]['extendedLocation']['customLocation'])
          elif kube.get("extendedLocation") and kube.get("extendedLocation").get("type") == "CustomLocation":
              parsed_custom_location_2 = parse_resource_id(kube["extendedLocation"]["name"])
  
          if parsed_custom_location_2 and (
              parsed_custom_location_2.get("name").lower() == custom_location_name.lower()) and (
                  parsed_custom_location_2.get("resource_group").lower() == resource_group.lower()):
              kube_environment_id = kube.get("id")
              break
  
      if not kube_environment_id:
          raise ResourceNotFoundError('Unable to find Kube Environment associated to the Custom Location')
  
      return kube_environment_id
  
  
  def _get_custom_location_id_from_custom_location(cmd, custom_location_name, resource_group_name):
      if is_valid_resource_id(custom_location_name):
          return custom_location_name
  
      kube_envs = KubeEnvironmentClient.list_by_resource_group(cmd=cmd, resource_group_name=resource_group_name)
  
      for kube in kube_envs:
          parsed_custom_location = None
          custom_location_id = None
  
          if kube.additional_properties and 'extendedLocation' in kube.additional_properties:
              custom_location_id = kube.additional_properties['extendedLocation'].get('name')
              parsed_custom_location = parse_resource_id(custom_location_id)
          elif kube.extended_location and kube.extended_location.custom_location:
              custom_location_id = kube.extended_location.custom_location
              parsed_custom_location = parse_resource_id(custom_location_id)
  
          if parsed_custom_location and parsed_custom_location.get("name").lower() == custom_location_name.lower():
              return custom_location_id
      return None
  
  
  def _get_custom_location_id_from_kube_env(kube):
      if kube.get("properties") and kube["properties"].get("extendedLocation"):
          return kube["properties"]['extendedLocation'].get('customLocation')
      if kube.get("extendedLocation") and kube["extendedLocation"].get("type") == "CustomLocation":
          return kube["extendedLocation"]["name"]
      raise CLIError("Could not get custom location from kube environment")
  
  
  def _ensure_kube_settings_in_json(appservice_plan_json, extended_location=None, kube_env=None):
      if appservice_plan_json.get("properties") and (appservice_plan_json["properties"].get("kubeEnvironmentProfile")
                                                     is None and kube_env is not None):
          appservice_plan_json["properties"]["kubeEnvironmentProfile"] = kube_env
  
      if appservice_plan_json.get("extendedLocation") is None and extended_location is not None:
          appservice_plan_json["extendedLocation"] = extended_location
  
  
  def create_app_service_plan_inner(cmd, resource_group_name, name, is_linux, hyper_v, per_site_scaling=False,
                                    custom_location=None, app_service_environment=None, sku=None,
                                    number_of_workers=None, location=None, tags=None, no_wait=False):
      HostingEnvironmentProfile, SkuDescription, AppServicePlan = cmd.get_models(
          'HostingEnvironmentProfile', 'SkuDescription', 'AppServicePlan')
  
      sku = _normalize_sku(sku)
      _validate_asp_sku(app_service_environment, custom_location, sku)
  
      if is_linux and hyper_v:
          raise MutuallyExclusiveArgumentError('Usage error: --is-linux and --hyper-v cannot be used together.')
  
      kube_environment = None
      kind = None
  
      client = web_client_factory(cmd.cli_ctx)
  
      if custom_location:
          kube_environment = _get_kube_env_from_custom_location(cmd, custom_location, resource_group_name)
  
      if app_service_environment:
          if hyper_v:
              raise ArgumentUsageError('Windows containers is not yet supported in app service environment')
          ase_id = _validate_app_service_environment_id(cmd.cli_ctx, app_service_environment, resource_group_name)
          ase_def = HostingEnvironmentProfile(id=ase_id)
          ase_list = client.app_service_environments.list()
          ase_found = False
          for ase in ase_list:
              if ase.name.lower() == app_service_environment.lower() or ase.id.lower() == ase_id.lower():
                  location = ase.location
                  ase_found = True
                  break
          if not ase_found:
              raise CLIError("App service environment '{}' not found in subscription.".format(ase_id))
      else:  # Non-ASE
          ase_def = None
  
      extended_location_envelope = None
      if kube_environment and (ase_def is None):
          kube_id = _resolve_kube_environment_id(cmd.cli_ctx, kube_environment, resource_group_name)
          # kube_def = KubeEnvironmentProfile(id=kube_id)
          kube_def = {"id": kube_id}
          kind = KUBE_ASP_KIND
          parsed_id = parse_resource_id(kube_id)
          kube_name = parsed_id.get("name")
          kube_rg = parsed_id.get("resource_group")
          if kube_name is not None and kube_rg is not None:
              kube_env = KubeEnvironmentClient.show(cmd=cmd, resource_group_name=kube_rg, name=kube_name)
              extended_location_envelope = {"name": _get_custom_location_id_from_kube_env(kube_env),
                                            "type": "CustomLocation"}
  
              if kube_env is not None:
                  location = kube_env["location"]
              else:
                  raise CLIError("Kube Environment '{}' not found in subscription.".format(kube_id))
      else:
          kube_def = None
  
      if location is None:
          location = _get_location_from_resource_group(cmd.cli_ctx, resource_group_name)
  
      # the api is odd on parameter naming, have to live with it for now
      sku_def = SkuDescription(tier=get_sku_name(sku), name=sku, capacity=number_of_workers)
  
      plan_def = AppServicePlan(location=location, tags=tags, sku=sku_def, kind=kind,
                                reserved=(is_linux or None), hyper_v=(hyper_v or None), name=name,
                                per_site_scaling=per_site_scaling, hosting_environment_profile=ase_def,
                                kube_environment_profile=kube_def, extended_location=extended_location_envelope)
      plan_json = plan_def.serialize()
      _ensure_kube_settings_in_json(appservice_plan_json=plan_json,
                                    extended_location=extended_location_envelope, kube_env=kube_def)
  
      return sdk_no_wait(no_wait, AppServiceClient.create, cmd=cmd, name=name,
                         resource_group_name=resource_group_name, appservice_json=plan_json)
  
  
  def update_app_service_plan(cmd, resource_group_name, name, sku=None, number_of_workers=None, no_wait=False):
      client = web_client_factory(cmd.cli_ctx)
      plan = client.app_service_plans.get(resource_group_name, name).serialize()
      plan_with_kube_env = AppServiceClient.show(cmd=cmd, name=name, resource_group_name=resource_group_name)
  
      if number_of_workers is None and sku is None:
          logger.warning('No update is done. Specify --sku and/or --number-of-workers.')
      sku_def = plan["sku"]
      if sku is not None:
          sku = _normalize_sku(sku)
          sku_def["tier"] = get_sku_name(sku)
          sku_def["name"] = sku
  
      if number_of_workers is not None:
          sku_def["capacity"] = number_of_workers
  
      plan["sku"] = sku_def
  
      _ensure_kube_settings_in_json(appservice_plan_json=plan,
                                    extended_location=plan_with_kube_env.get("extendedLocation"),
                                    kube_env=plan_with_kube_env["properties"].get("kubeEnvironmentProfile"))
  
      return sdk_no_wait(no_wait, AppServiceClient.update, cmd=cmd, name=name,
                         resource_group_name=resource_group_name, appservice_json=plan)
  
  
  def _validate_asp_and_custom_location_kube_envs_match(cmd, resource_group_name, custom_location, plan):
      if is_valid_resource_id(plan):
          parse_result = parse_resource_id(plan)
          plan_info = AppServiceClient.show(cmd=cmd, name=parse_result['name'],
                                            resource_group_name=parse_result["resource_group"])
      else:
          plan_info = AppServiceClient.show(cmd=cmd, name=plan, resource_group_name=resource_group_name)
      if not plan_info:
          raise CLIError("The plan '{}' doesn't exist in the resource group '{}".format(plan, resource_group_name))
  
      plan_kube_env_id = ""
      custom_location_kube_env_id = _get_kube_env_from_custom_location(cmd, custom_location, resource_group_name)
      if plan_info["properties"].get("kubeEnvironmentProfile"):
          plan_kube_env_id = plan_info["properties"]["kubeEnvironmentProfile"]["id"]
  
      return plan_kube_env_id.lower() == custom_location_kube_env_id.lower()
  
  
  def _should_create_new_appservice_plan_for_k8se(cmd, name, custom_location, plan, resource_group_name):
      if custom_location and plan:
          return False
      if custom_location:
          existing_app_details = get_app_details(cmd, name, resource_group_name)
          if not existing_app_details:
              return True
          if _validate_asp_and_custom_location_kube_envs_match(cmd, resource_group_name, custom_location,
                                                               existing_app_details.server_farm_id):
              return False  # existing app and kube environments match
          return True  # existing app but new custom location
      return False  # plan is not None
  
  
  def _is_webapp_kube(custom_location, plan_info, SkuDescription):
      return custom_location or plan_info.kind.upper() == KUBE_ASP_KIND.upper() or (
          isinstance(plan_info.sku, SkuDescription) and plan_info.sku.name.upper() == KUBE_DEFAULT_SKU)
  
  
  def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custom_location=None, startup_file=None,  # pylint: disable=too-many-statements,too-many-branches
                    deployment_container_image_name=None, deployment_source_url=None, deployment_source_branch='master',
                    deployment_local_git=None, docker_registry_server_password=None, docker_registry_server_user=None,
                    multicontainer_config_type=None, multicontainer_config_file=None, tags=None,
                    using_webapp_up=False, language=None, assign_identities=None, role='Contributor', scope=None,
                    min_worker_count=None, max_worker_count=None):
      SiteConfig, SkuDescription, Site, NameValuePair, AppServicePlan = cmd.get_models(
          'SiteConfig', 'SkuDescription', 'Site', 'NameValuePair', "AppServicePlan")
      if deployment_source_url and deployment_local_git:
          raise CLIError('usage error: --deployment-source-url <url> | --deployment-local-git')
  
      if not plan and not custom_location:
          raise RequiredArgumentMissingError("Either Plan or Custom Location must be specified")
  
      # This is to keep the existing appsettings for a newly created webapp on existing webapp name.
      name_validation = get_site_availability(cmd, name)
      if not name_validation.name_available:
          if name_validation.reason == 'Invalid':
              raise CLIError(name_validation.message)
          logger.warning("Webapp '%s' already exists. The command will use the existing app's settings.", name)
          app_details = get_app_details(cmd, name, resource_group_name)
          if app_details is None:
              raise CLIError("Unable to retrieve details of the existing app '{}'. Please check that "
                             "the app is a part of the current subscription".format(name))
          current_rg = app_details.resource_group
          if resource_group_name is not None and (resource_group_name.lower() != current_rg.lower()):
              raise CLIError("The webapp '{}' exists in resource group '{}' and does not "
                             "match the value entered '{}'. Please re-run command with the "
                             "correct parameters.". format(name, current_rg, resource_group_name))
          existing_app_settings = _generic_site_operation(cmd.cli_ctx, resource_group_name,
                                                          name, 'list_application_settings')
          settings = []
          for k, v in existing_app_settings.properties.items():
              settings.append(NameValuePair(name=k, value=v))
          site_config = SiteConfig(app_settings=settings)
      else:
          site_config = SiteConfig(app_settings=[])
  
      _should_create_new_plan = _should_create_new_appservice_plan_for_k8se(cmd,
                                                                            name, custom_location,
                                                                            plan, resource_group_name)
      if _should_create_new_plan:
          plan = generate_default_app_service_plan_name(name)
          logger.warning("Plan not specified. Creating Plan '%s' with sku '%s'", plan, KUBE_DEFAULT_SKU)
          create_app_service_plan(cmd=cmd, resource_group_name=resource_group_name,
                                  name=plan, is_linux=True, hyper_v=False, custom_location=custom_location,
                                  per_site_scaling=True, number_of_workers=1)
  
      if custom_location and plan:
          if not _validate_asp_and_custom_location_kube_envs_match(cmd, resource_group_name, custom_location, plan):
              raise ValidationError("Custom location's kube environment and App Service Plan's "
                                    "kube environment don't match")
      elif custom_location and not plan:
          app_details = get_app_details(cmd, name, resource_group_name)
          if app_details is not None:
              plan = app_details.server_farm_id
  
      docker_registry_server_url = parse_docker_image_name(deployment_container_image_name)
  
      client = web_client_factory(cmd.cli_ctx)
      if is_valid_resource_id(plan):
          parse_result = parse_resource_id(plan)
          plan_info = AppServicePlan.from_dict(AppServiceClient.show(cmd=cmd, name=parse_result['name'],
                                                                     resource_group_name=parse_result["resource_group"]))
      else:
          plan_info = AppServicePlan.from_dict(AppServiceClient.show(cmd=cmd,
                                                                     name=plan, resource_group_name=resource_group_name))
      if not plan_info:
          raise CLIError("The plan '{}' doesn't exist in the resource group '{}".format(plan, resource_group_name))
  
      if custom_location:
          _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',
                                                                                          'B1', 'B2', 'B3', 'BASIC']:
          site_config.always_on = True
      webapp_def = Site(location=location, site_config=site_config, server_farm_id=plan_info.id, tags=tags,
                        https_only=using_webapp_up)
  
      is_kube = _is_webapp_kube(custom_location, plan_info, SkuDescription)
      if is_kube:
          if deployment_container_image_name:
              webapp_def.kind = KUBE_CONTAINER_APP_KIND
          else:
              webapp_def.kind = KUBE_APP_KIND
  
          # if Custom Location provided, use that for Extended Location Envelope. Otherwise, get Custom Location from ASP
          if custom_location:
              webapp_def.enable_additional_properties_sending()
              custom_location_id = _get_custom_location_id_from_custom_location(cmd, custom_location, resource_group_name)
              if custom_location_id:
                  extended_loc = {'name': custom_location_id, 'type': 'CustomLocation'}
                  webapp_def.additional_properties["extendedLocation"] = extended_loc
          else:
              extended_loc = plan_info.additional_properties["extendedLocation"]
              webapp_def.additional_properties["extendedLocation"] = extended_loc
  
      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 deployment_container_image_name:
              site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_URL',
                                                            value=docker_registry_server_url))
  
              if docker_registry_server_user is not None and docker_registry_server_password is not None:
                  site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_USERNAME',
                                                                value=docker_registry_server_user))
  
                  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))
      if runtime:
          runtime = helper.remove_delimiters(runtime)
  
      current_stack = None
      if is_linux or is_kube:
          if not validate_container_app_create_options(runtime, deployment_container_image_name,
                                                       multicontainer_config_type, multicontainer_config_file):
              raise CLIError("usage error: --runtime | --deployment-container-image-name |"
                             " --multicontainer-config-type TYPE --multicontainer-config-file FILE")
          if startup_file:
              site_config.app_command_line = startup_file
  
          if runtime:
              site_config.linux_fx_version = runtime
              match = helper.resolve(runtime)
              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)
          elif deployment_container_image_name:
              site_config.linux_fx_version = _format_fx_version(deployment_container_image_name)
              if name_validation.name_available:
                  site_config.app_settings.append(NameValuePair(name="WEBSITES_ENABLE_APP_SERVICE_STORAGE",
                                                                value="false"))
          elif multicontainer_config_type and multicontainer_config_file:
              encoded_config_file = _get_linux_multicontainer_encoded_config_from_file(multicontainer_config_file)
              site_config.linux_fx_version = _format_fx_version(encoded_config_file, multicontainer_config_type)
  
      elif plan_info.is_xenon:  # windows container webapp
          if deployment_container_image_name:
              site_config.windows_fx_version = _format_fx_version(deployment_container_image_name)
          # set the needed app settings for container image validation
          if name_validation.name_available:
              site_config.app_settings.append(NameValuePair(name="DOCKER_REGISTRY_SERVER_USERNAME",
                                                            value=docker_registry_server_user))
              site_config.app_settings.append(NameValuePair(name="DOCKER_REGISTRY_SERVER_PASSWORD",
                                                            value=docker_registry_server_password))
              site_config.app_settings.append(NameValuePair(name="DOCKER_REGISTRY_SERVER_URL",
                                                            value=docker_registry_server_url))
  
      elif runtime:  # windows webapp with runtime specified
          if any([startup_file, deployment_container_image_name, multicontainer_config_file, multicontainer_config_type]):
              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)
          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)
  
          # 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
              site_config.app_settings.append(NameValuePair(name="WEBSITE_NODE_DEFAULT_VERSION",
                                                            value=node_default_version))
  
      if site_config.app_settings:
          for setting in site_config.app_settings:
              logger.info('Will set appsetting %s', setting)
      if using_webapp_up:  # when the routine is invoked as a help method for webapp up
          if name_validation.name_available:
              logger.info("will set appsetting for enabling build")
              site_config.app_settings.append(NameValuePair(name="SCM_DO_BUILD_DURING_DEPLOYMENT", value=True))
      if language is not None and language.lower() == 'dotnetcore':
          if name_validation.name_available:
              site_config.app_settings.append(NameValuePair(name='ANCM_ADDITIONAL_ERROR_PAGE_LINK',
                                                            value='https://{}.scm.azurewebsites.net/detectors'
                                                            .format(name)))
  
      poller = client.web_apps.begin_create_or_update(resource_group_name, name, webapp_def)
      webapp = LongRunningOperation(cmd.cli_ctx)(poller)
  
      if deployment_container_image_name:
          update_container_settings(cmd, resource_group_name, name, docker_registry_server_url,
                                    deployment_container_image_name, docker_registry_server_user,
                                    docker_registry_server_password=docker_registry_server_password)
  
      if is_kube:
          return webapp
  
      if current_stack:
          _update_webapp_current_stack_property_if_needed(cmd, resource_group_name, name, current_stack)
  
      # Ensure SCC operations follow right after the 'create', no precedent appsetting update commands
      _set_remote_or_local_git(cmd, webapp, resource_group_name, name, deployment_source_url,
                               deployment_source_branch, deployment_local_git)
  
      _fill_ftp_publishing_url(cmd, webapp, resource_group_name, name)
  
      if assign_identities is not None:
          identity = assign_identity(cmd, resource_group_name, name, assign_identities,
                                     role, None, scope)
          webapp.identity = identity
  
      return webapp
  
  
  def scale_webapp(cmd, resource_group_name, name, instance_count, slot=None):
      return update_site_configs(cmd, resource_group_name, name,
                                 number_of_workers=instance_count, slot=slot)
  
  
  def show_webapp(cmd, resource_group_name, name, slot=None, app_instance=None):
      webapp = app_instance
      if not app_instance:  # when the routine is invoked as a help method, not through commands
          webapp = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get', slot)
      if not webapp:
          raise ResourceNotFoundError("WebApp'{}', is not found on RG '{}'.".format(name, resource_group_name))
      webapp.site_config = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get_configuration', slot)
      _rename_server_farm_props(webapp)
  
      # TODO: get rid of this conditional once the api's are implemented for kubeapps
      if KUBE_APP_KIND.lower() not in webapp.kind.lower():
          _fill_ftp_publishing_url(cmd, webapp, resource_group_name, name, slot)
  
      return webapp
  
  
  def _is_function_kube(custom_location, plan_info, SkuDescription):
      return custom_location or plan_info is not None and (
          plan_info.kind.upper() == KUBE_ASP_KIND.upper() or (
              isinstance(plan_info.sku, SkuDescription) and plan_info.sku.name.upper() == KUBE_DEFAULT_SKU))
  
  
  def create_function(cmd, resource_group_name, name, storage_account, plan=None, custom_location=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,
                      min_worker_count=None, max_worker_count=None):
      # pylint: disable=too-many-statements, too-many-branches
      SkuDescription = cmd.get_models('SkuDescription')
      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 not plan and not consumption_plan_location and not custom_location:
          raise RequiredArgumentMissingError("Either Plan, Consumption Plan or Custom Location must be specified")
  
      if consumption_plan_location and custom_location:
          raise MutuallyExclusiveArgumentError("Consumption Plan and Custom Location cannot be used together")
  
      if consumption_plan_location and plan:
          raise MutuallyExclusiveArgumentError("Consumption Plan and Plan cannot be used together")
  
      SiteConfig, Site, NameValuePair = cmd.get_models('SiteConfig', 'Site', 'NameValuePair')
      docker_registry_server_url = parse_docker_image_name(deployment_container_image_name)
  
      site_config = SiteConfig(app_settings=[])
      functionapp_def = Site(location=None, site_config=site_config, tags=tags)
      client = web_client_factory(cmd.cli_ctx)
      plan_info = None
      if runtime is not None:
          runtime = runtime.lower()
  
      if consumption_plan_location:
          locations = list_consumption_locations(cmd)
          location = next((loc for loc in locations if loc['name'].lower() == consumption_plan_location.lower()), None)
          if location is None:
              raise CLIError("Location is invalid. Use: az functionapp list-consumption-locations")
          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'
  
      else:  # apps with SKU based plan
          _should_create_new_plan = _should_create_new_appservice_plan_for_k8se(cmd,
                                                                                name, custom_location,
                                                                                plan, resource_group_name)
          if _should_create_new_plan:
              plan = generate_default_app_service_plan_name(name)
              logger.warning("Plan not specified. Creating Plan '%s' with sku '%s'", plan, KUBE_DEFAULT_SKU)
              create_app_service_plan(cmd=cmd, resource_group_name=resource_group_name,
                                      name=plan, is_linux=True, hyper_v=False, custom_location=custom_location,
                                      per_site_scaling=True, number_of_workers=1)
  
          if custom_location and plan:
              if not _validate_asp_and_custom_location_kube_envs_match(cmd, resource_group_name, custom_location, plan):
                  raise ValidationError("Custom location's kube environment "
                                        "and App Service Plan's kube environment don't match")
          elif custom_location and not plan:
              app_details = get_app_details(cmd, name, resource_group_name)
              if app_details is not None:
                  plan = app_details.server_farm_id
  
          if is_valid_resource_id(plan):
              parse_result = parse_resource_id(plan)
              plan_info = client.app_service_plans.get(parse_result['resource_group'], parse_result['name'])
          else:
              plan_info = client.app_service_plans.get(resource_group_name, plan)
          if not plan_info:
              raise CLIError("The plan '{}' doesn't exist".format(plan))
          location = plan_info.location
          is_linux = plan_info.reserved
          functionapp_def.server_farm_id = plan_info.id
          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 is_linux and not runtime and (consumption_plan_location or not deployment_container_image_name):
          raise CLIError(
              "usage error: --runtime RUNTIME required for linux functions apps without custom image.")
  
      if runtime:
          if is_linux and runtime not in LINUX_RUNTIMES:
              raise CLIError("usage error: Currently supported runtimes (--runtime) in linux function apps are: {}."
                             .format(', '.join(LINUX_RUNTIMES)))
          if not is_linux and runtime not in WINDOWS_RUNTIMES:
              raise CLIError("usage error: Currently supported runtimes (--runtime) in windows function apps are: {}."
                             .format(', '.join(WINDOWS_RUNTIMES)))
          site_config.app_settings.append(NameValuePair(name='FUNCTIONS_WORKER_RUNTIME', value=runtime))
  
      if runtime_version is not None:
          if runtime is None:
              raise CLIError('Must specify --runtime to use --runtime-version')
          allowed_versions = FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS[functions_version][runtime]
          if runtime_version not in allowed_versions:
              if runtime == 'dotnet':
                  raise CLIError('--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 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)))
          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.',
                             FUNCTIONS_VERSION_TO_DEFAULT_RUNTIME_VERSION[functions_version][runtime])
  
      con_string = _validate_and_get_connection_string(cmd.cli_ctx, resource_group_name, storage_account)
  
      if is_kube:
          functionapp_def.enable_additional_properties_sending()
          # if Custom Location provided, use that for Extended Location Envelope. Otherwise, get Custom Location from ASP
          if custom_location:
              custom_location_id = _get_custom_location_id_from_custom_location(cmd, custom_location, resource_group_name)
              if custom_location_id:
                  extended_loc = {'name': custom_location_id, 'type': 'CustomLocation'}
                  functionapp_def.additional_properties["extendedLocation"] = extended_loc
          else:
              extended_loc = plan_info.additional_properties["extendedLocation"]
              functionapp_def.additional_properties["extendedLocation"] = extended_loc
  
          functionapp_def.kind = KUBE_FUNCTION_APP_KIND
          functionapp_def.reserved = True
          site_config.app_settings.append(NameValuePair(name='WEBSITES_PORT', value='80'))
          site_config.app_settings.append(NameValuePair(name='MACHINEKEY_DecryptionKey',
                                                        value=str(hexlify(urandom(32)).decode()).upper()))
          if deployment_container_image_name:
              functionapp_def.kind = KUBE_FUNCTION_CONTAINER_APP_KIND
              site_config.app_settings.append(NameValuePair(name='DOCKER_CUSTOM_IMAGE_NAME',
                                                            value=deployment_container_image_name))
              site_config.app_settings.append(NameValuePair(name='FUNCTION_APP_EDIT_MODE', value='readOnly'))
              site_config.linux_fx_version = _format_fx_version(deployment_container_image_name)
              site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_URL',
                                              value=docker_registry_server_url))
              if docker_registry_server_user is not None and docker_registry_server_password is not None:
                  site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_USERNAME',
                                                  value=docker_registry_server_user))
                  site_config.app_settings.append(NameValuePair(name='DOCKER_REGISTRY_SERVER_PASSWORD',
                                                  value=docker_registry_server_password))
          else:
              site_config.app_settings.append(NameValuePair(name='WEBSITES_ENABLE_APP_SERVICE_STORAGE', value='true'))
              site_config.linux_fx_version = _get_linux_fx_kube_functionapp(runtime, runtime_version)
      elif is_linux:
          functionapp_def.kind = 'functionapp,linux'
          functionapp_def.reserved = True
          is_consumption = consumption_plan_location is not None
          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:
                  functionapp_def.kind = 'functionapp,linux,container'
                  site_config.app_settings.append(NameValuePair(name='DOCKER_CUSTOM_IMAGE_NAME',
                                                                value=deployment_container_image_name))
                  site_config.app_settings.append(NameValuePair(name='FUNCTION_APP_EDIT_MODE', value='readOnly'))
                  site_config.app_settings.append(NameValuePair(name='WEBSITES_ENABLE_APP_SERVICE_STORAGE',
                                                                value='false'))
                  site_config.linux_fx_version = _format_fx_version(deployment_container_image_name)
              else:
                  site_config.app_settings.append(NameValuePair(name='WEBSITES_ENABLE_APP_SERVICE_STORAGE',
                                                                value='true'))
                  if runtime not in FUNCTIONS_VERSION_TO_SUPPORTED_RUNTIME_VERSIONS[functions_version]:
                      raise CLIError("An appropriate linux image for runtime:'{}', "
                                     "functions_version: '{}' was not found".format(runtime, functions_version))
          if deployment_container_image_name is None:
              site_config.linux_fx_version = _get_linux_fx_functionapp(functions_version, runtime, runtime_version)
      else:
          functionapp_def.kind = 'functionapp'
          if runtime == "java":
              site_config.java_version = _get_java_version_functionapp(functions_version, runtime_version)
  
      # 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(functions_version,
                                                                                                runtime,
                                                                                                runtime_version)))
  
      # If plan is not consumption or elastic premium, we need to set always on
      if consumption_plan_location is None and not is_plan_elastic_premium(cmd, plan_info):
          site_config.always_on = True
  
      # If plan is elastic premium or windows consumption, we need these app settings
      is_windows_consumption = consumption_plan_location is not None and not is_linux
      if is_plan_elastic_premium(cmd, plan_info) or is_windows_consumption:
          site_config.app_settings.append(NameValuePair(name='WEBSITE_CONTENTAZUREFILECONNECTIONSTRING',
                                                        value=con_string))
          site_config.app_settings.append(NameValuePair(name='WEBSITE_CONTENTSHARE', value=name.lower()))
  
      create_app_insights = False
  
      if app_insights_key is not None:
          site_config.app_settings.append(NameValuePair(name='APPINSIGHTS_INSTRUMENTATIONKEY',
                                                        value=app_insights_key))
      elif app_insights is not None:
          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 not disable_app_insights:
          create_app_insights = True
  
      poller = client.web_apps.begin_create_or_update(resource_group_name, name, functionapp_def)
      functionapp = LongRunningOperation(cmd.cli_ctx)(poller)
  
      if consumption_plan_location and is_linux:
          logger.warning("Your Linux function app '%s', that uses a consumption plan has been successfully "
                         "created but is not active until content is published using "
                         "Azure Portal or the Functions Core Tools.", name)
      else:
          _set_remote_or_local_git(cmd, functionapp, resource_group_name, name, deployment_source_url,
                                   deployment_source_branch, deployment_local_git)
  
      if create_app_insights:
          try:
              try_create_application_insights(cmd, functionapp)
          except Exception:  # pylint: disable=broad-except
              logger.warning('Error while trying to create and configure an Application Insights for the Function App. '
                             'Please use the Azure Portal to create and configure the Application Insights, if needed.')
  
      if deployment_container_image_name:
          update_container_settings_functionapp(cmd, resource_group_name, name, docker_registry_server_url,
                                                deployment_container_image_name, docker_registry_server_user,
                                                docker_registry_server_password)
  
      return functionapp
  
  
  def update_container_settings_functionapp(cmd, resource_group_name, name, docker_registry_server_url=None,
                                            docker_custom_image_name=None, docker_registry_server_user=None,
                                            docker_registry_server_password=None, slot=None):
      return update_container_settings(cmd, resource_group_name, name, docker_registry_server_url,
                                       docker_custom_image_name, docker_registry_server_user, None,
                                       docker_registry_server_password, multicontainer_config_type=None,
                                       multicontainer_config_file=None, slot=slot)
  
  
  def try_create_application_insights(cmd, functionapp):
      creation_failed_warn = 'Unable to create the Application Insights for the Function App. ' \
                             'Please use the Azure Portal to manually create and configure the Application Insights, ' \
                             'if needed.'
  
      ai_resource_group_name = functionapp.resource_group
      ai_name = functionapp.name
      ai_location = functionapp.location
  
      app_insights_client = get_mgmt_service_client(cmd.cli_ctx, ApplicationInsightsManagementClient)
      ai_properties = {
          "name": ai_name,
          "location": ai_location,
          "kind": "web",
          "properties": {
              "Application_Type": "web"
          }
      }
      appinsights = app_insights_client.components.create_or_update(ai_resource_group_name, ai_name, ai_properties)
      if appinsights is None or appinsights.instrumentation_key is None:
          logger.warning(creation_failed_warn)
          return
  
      # We make this success message as a warning to no interfere with regular JSON output in stdout
      logger.warning('Application Insights \"%s\" was created for this Function App. '
                     'You can visit https://portal.azure.com/#resource%s/overview to view your '
                     'Application Insights component', appinsights.name, appinsights.id)
  
      update_app_settings(cmd, functionapp.resource_group, functionapp.name,
                          ['APPINSIGHTS_INSTRUMENTATIONKEY={}'.format(appinsights.instrumentation_key)])
  
  
  # for any modifications to the non-optional parameters, adjust the reflection logic accordingly
  # in the method
  # pylint: disable=unused-argument
  def update_site_configs(cmd, resource_group_name, name, slot=None, number_of_workers=None, linux_fx_version=None,
                          windows_fx_version=None, pre_warmed_instance_count=None, php_version=None,
                          python_version=None, net_framework_version=None,
                          java_version=None, java_container=None, java_container_version=None,
                          remote_debugging_enabled=None, web_sockets_enabled=None,
                          always_on=None, auto_heal_enabled=None,
                          use32_bit_worker_process=None,
                          min_tls_version=None,
                          http20_enabled=None,
                          app_command_line=None,
                          ftps_state=None,
                          generic_configurations=None):
      configs = get_site_configs(cmd, resource_group_name, name, slot)
      if number_of_workers is not None:
          number_of_workers = validate_range_of_int_flag('--number-of-workers', number_of_workers, min_val=0, max_val=20)
      if linux_fx_version:
          if linux_fx_version.strip().lower().startswith('docker|'):
              update_app_settings(cmd, resource_group_name, name, ["WEBSITES_ENABLE_APP_SERVICE_STORAGE=false"])
          else:
              delete_app_settings(cmd, resource_group_name, name, ["WEBSITES_ENABLE_APP_SERVICE_STORAGE"])
  
      if pre_warmed_instance_count is not None:
          pre_warmed_instance_count = validate_range_of_int_flag('--prewarmed-instance-count', pre_warmed_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 = ['pre_warmed_instance_count', 'number_of_workers']
      # 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')
  
      generic_configurations = generic_configurations or []
  
      # https://github.com/Azure/azure-cli/issues/14857
      updating_ip_security_restrictions = False
  
      result = {}
      for s in generic_configurations:
          try:
              json_object = get_json_object(s)
              for config_name in json_object:
                  if config_name.lower() == 'ip_security_restrictions':
                      updating_ip_security_restrictions = True
              result.update(json_object)
          except CLIError:
              config_name, value = s.split('=', 1)
              result[config_name] = value
  
      for config_name, value in result.items():
          if config_name.lower() == 'ip_security_restrictions':
              updating_ip_security_restrictions = True
          setattr(configs, config_name, value)
  
      if not updating_ip_security_restrictions:
          setattr(configs, 'ip_security_restrictions', None)
  
      return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update_configuration', slot, configs)
  
  
  def config_source_control(cmd, resource_group_name, name, repo_url, repository_type='git', branch=None,  # pylint: disable=too-many-locals
                            manual_integration=None, git_token=None, slot=None):
      client = web_client_factory(cmd.cli_ctx)
      location = _get_location_from_webapp(client, resource_group_name, name)
  
      from azure.mgmt.web.models import SiteSourceControl, SourceControl
      if git_token:
          sc = SourceControl(location=location, source_control_name='GitHub', token=git_token)
          client.update_source_control('GitHub', sc)
  
      source_control = SiteSourceControl(location=location, repo_url=repo_url, branch=branch,
                                         is_manual_integration=manual_integration,
                                         is_mercurial=(repository_type != 'git'))
  
      # SCC config can fail if previous commands caused SCMSite shutdown, so retry here.
      for i in range(5):
          try:
              poller = _generic_site_operation(cmd.cli_ctx, resource_group_name, name,
                                               'create_or_update_source_control',
                                               slot, source_control)
              return LongRunningOperation(cmd.cli_ctx)(poller)
          except Exception as ex:  # pylint: disable=broad-except
              import re
              ex = ex_handler_factory(no_throw=True)(ex)
              # for non server errors(50x), just throw; otherwise retry 4 times
              if i == 4 or not re.findall(r'\(50\d\)', str(ex)):
                  raise
              logger.warning('retrying %s/4', i + 1)
              time.sleep(5)   # retry in a moment
      return None
  
  
  def list_publish_profiles(cmd, resource_group_name, name, slot=None, xml=False):
      import xmltodict
  
      content = _generic_site_operation(cmd.cli_ctx, resource_group_name, name,
                                        'list_publishing_profile_xml_with_secrets', slot, {"format": "WebDeploy"})
      full_xml = ''
      for f in content:
          full_xml += f.decode()
  
      if not xml:
          profiles = xmltodict.parse(full_xml, xml_attribs=True)['publishData']['publishProfile']
          converted = []
  
          if not isinstance(profiles, list):
              profiles = [profiles]
  
          for profile in profiles:
              new = {}
              for key in profile:
                  # strip the leading '@' xmltodict put in for attributes
                  new[key.lstrip('@')] = profile[key]
              converted.append(new)
          return converted
      cmd.cli_ctx.invocation.data['output'] = 'tsv'
      return full_xml
  
  
  # private helpers
  
  def _resolve_kube_environment_id(cli_ctx, kube_environment, resource_group_name):
      if is_valid_resource_id(kube_environment):
          return kube_environment
  
      from msrestazure.tools import resource_id
      return resource_id(
          subscription=get_subscription_id(cli_ctx),
          resource_group=resource_group_name,
          namespace='Microsoft.Web',
          type='kubeEnvironments',
          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:
          logger.warning("Linking to git repository '%s'", deployment_source_url)
          try:
              config_source_control(cmd, resource_group_name, name, deployment_source_url, 'git',
                                    deployment_source_branch, manual_integration=True)
          except Exception as ex:  # pylint: disable=broad-except
              ex = ex_handler_factory(no_throw=True)(ex)
              logger.warning("Link to git repository failed due to error '%s'", ex)
  
      if deployment_local_git:
          local_git_info = enable_local_git(cmd, resource_group_name, name)
          logger.warning("Local git is configured with url of '%s'", local_git_info['url'])
          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)
      settings_url = '{}/api/settings'.format(scm_url)
      username, password = _get_site_credential(cmd.cli_ctx, resource_group_name, name, slot)
      headers = {
          'Content-Type': 'application/octet-stream',
          'Cache-Control': 'no-cache',
          'User-Agent': get_az_user_agent()
      }
  
      import requests
      response = requests.get(settings_url, headers=headers, auth=(username, password), timeout=3)
  
      return response.json() or {}
  
  
  # Check if the app setting is propagated to the Kudu site correctly by calling api/settings endpoint
  # should_have [] is a list of app settings which are expected to be set
  # should_not_have [] is a list of app settings which are expected to be absent
  # should_contain {} is a dictionary of app settings which are expected to be set with precise values
  # Return True if validation succeeded
  def validate_app_settings_in_scm(cmd, resource_group_name, name, slot=None,
                                   should_have=None, should_not_have=None, should_contain=None):
      scm_settings = _get_app_settings_from_scm(cmd, resource_group_name, name, slot)
      scm_setting_keys = set(scm_settings.keys())
  
      if should_have and not set(should_have).issubset(scm_setting_keys):
          return False
  
      if should_not_have and set(should_not_have).intersection(scm_setting_keys):
          return False
  
      temp_setting = scm_settings.copy()
      temp_setting.update(should_contain or {})
      if temp_setting != scm_settings:
          return False
  
      return True
  
  
  def remove_remote_build_app_settings(cmd, resource_group_name, name, slot):
      settings = get_app_settings(cmd, resource_group_name, name, slot)
      scm_do_build_during_deployment = None
  
      app_settings_should_contain = {}
  
      for keyval in settings:
          value = keyval['value'].lower()
          if keyval['name'] == 'SCM_DO_BUILD_DURING_DEPLOYMENT':
              scm_do_build_during_deployment = value in ('true', '1')
  
      if scm_do_build_during_deployment is not False:
          logger.warning("Setting SCM_DO_BUILD_DURING_DEPLOYMENT to false")
          update_app_settings(cmd, resource_group_name, name, [
              "SCM_DO_BUILD_DURING_DEPLOYMENT=false"
          ], slot)
          app_settings_should_contain['SCM_DO_BUILD_DURING_DEPLOYMENT'] = 'false'
  
      # Wait for scm site to get the latest app settings
      if app_settings_should_contain:
          logger.warning("Waiting SCM site to be updated with the latest app settings")
          scm_is_up_to_date = False
          retries = 10
          while not scm_is_up_to_date and retries >= 0:
              scm_is_up_to_date = validate_app_settings_in_scm(
                  cmd, resource_group_name, name, slot,
                  should_contain=app_settings_should_contain)
              retries -= 1
              time.sleep(5)
  
          if retries < 0:
              logger.warning("App settings may not be propagated to the SCM site")
  
  
  def add_remote_build_app_settings(cmd, resource_group_name, name, slot):
      settings = get_app_settings(cmd, resource_group_name, name, slot)
      scm_do_build_during_deployment = None
      website_run_from_package = None
      enable_oryx_build = None
  
      app_settings_should_not_have = []
      app_settings_should_contain = {}
  
      for keyval in settings:
          value = keyval['value'].lower()
          if keyval['name'] == 'SCM_DO_BUILD_DURING_DEPLOYMENT':
              scm_do_build_during_deployment = value in ('true', '1')
          if keyval['name'] == 'WEBSITE_RUN_FROM_PACKAGE':
              website_run_from_package = value
          if keyval['name'] == 'ENABLE_ORYX_BUILD':
              enable_oryx_build = value
  
      if scm_do_build_during_deployment is not True:
          logger.warning("Setting SCM_DO_BUILD_DURING_DEPLOYMENT to true")
          update_app_settings(cmd, resource_group_name, name, [
              "SCM_DO_BUILD_DURING_DEPLOYMENT=true"
          ], slot)
          app_settings_should_contain['SCM_DO_BUILD_DURING_DEPLOYMENT'] = 'true'
  
      if website_run_from_package:
          logger.warning("Removing WEBSITE_RUN_FROM_PACKAGE app setting")
          delete_app_settings(cmd, resource_group_name, name, [
              "WEBSITE_RUN_FROM_PACKAGE"
          ], slot)
          app_settings_should_not_have.append('WEBSITE_RUN_FROM_PACKAGE')
  
      if enable_oryx_build:
          logger.warning("Removing ENABLE_ORYX_BUILD app setting")
          delete_app_settings(cmd, resource_group_name, name, [
              "ENABLE_ORYX_BUILD"
          ], slot)
          app_settings_should_not_have.append('ENABLE_ORYX_BUILD')
  
      # Wait for scm site to get the latest app settings
      if app_settings_should_not_have or app_settings_should_contain:
          logger.warning("Waiting SCM site to be updated with the latest app settings")
          scm_is_up_to_date = False
          retries = 10
          while not scm_is_up_to_date and retries >= 0:
              scm_is_up_to_date = validate_app_settings_in_scm(
                  cmd, resource_group_name, name, slot,
                  should_contain=app_settings_should_contain,
                  should_not_have=app_settings_should_not_have)
              retries -= 1
              time.sleep(5)
  
          if retries < 0:
              logger.warning("App settings may not be propagated to the SCM site.")
  
  
  def enable_zip_deploy_functionapp(cmd, resource_group_name, name, src, build_remote=False, timeout=None, slot=None):
      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))
      parse_plan_id = parse_resource_id(app.server_farm_id)
      plan_info = None
      retry_delay = 10  # seconds
      # We need to retry getting the plan because sometimes if the plan is created as part of function app,
      # it can take a couple of tries before it gets the plan
      for _ in range(5):
          plan_info = client.app_service_plans.get(parse_plan_id['resource_group'],
                                                   parse_plan_id['name'])
          if plan_info is not None:
              break
          time.sleep(retry_delay)
  
      if build_remote and not app.reserved:
          raise CLIError('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:
          return upload_zip_to_storage(cmd, resource_group_name, name, src, slot)
      if build_remote:
          add_remote_build_app_settings(cmd, resource_group_name, name, slot)
      else:
          remove_remote_build_app_settings(cmd, resource_group_name, name, slot)
  
      return enable_zip_deploy(cmd, resource_group_name, name, src, timeout, slot)
  
  
  def enable_zip_deploy_webapp(cmd, resource_group_name, name, src, timeout=None, slot=None, is_kube=False):
      return enable_zip_deploy(cmd, resource_group_name, name, src, timeout=timeout, slot=slot, is_kube=is_kube)
  
  
  def enable_zip_deploy(cmd, resource_group_name, name, src, timeout=None, slot=None, is_kube=False):
      logger.warning("Getting scm site credentials for zip deployment")
      user_name, password = _get_site_credential(cmd.cli_ctx, resource_group_name, name, slot)
      # Wait for a few seconds for envoy changes to propogate, for a kube app
      if is_kube:
          time.sleep(7)
      try:
          scm_url = _get_scm_url(cmd, resource_group_name, name, slot)
      except ValueError as e:
          raise CLIError('Failed to fetch scm url for function app') from e
  
      zip_url = scm_url + '/api/zipdeploy?isAsync=true'
      deployment_status_url = scm_url + '/api/deployments/latest'
  
      import urllib3
      authorization = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(user_name, password))
      headers = authorization
      headers['Content-Type'] = 'application/octet-stream'
      headers['Cache-Control'] = 'no-cache'
      headers['User-Agent'] = get_az_user_agent()
  
      import requests
      import os
      from azure.cli.core.util import should_disable_connection_verify
      # Read file content
      with open(os.path.realpath(os.path.expanduser(src)), 'rb') as fs:
          zip_content = fs.read()
          logger.warning("Starting zip deployment. This operation can take a while to complete ...")
          res = requests.post(zip_url, data=zip_content, headers=headers, verify=not should_disable_connection_verify())
          logger.warning("Deployment endpoint responded with status code %d", res.status_code)
  
          if is_kube and res.status_code != 202 and res.status_code != 409:
              logger.warning('Something went wrong. It may take a few seconds for a new deployment to reflect'
                             'on kube cluster. Retrying deployment...')
              time.sleep(10)   # retry in a moment
              res = requests.post(zip_url, data=zip_content, headers=headers,
                                  verify=not should_disable_connection_verify())
              logger.warning("Deployment endpoint responded with status code %d", res.status_code)
  
      # check if there's an ongoing process
      if res.status_code == 409:
          raise CLIError("There may be an ongoing deployment or your app setting has WEBSITE_RUN_FROM_PACKAGE. "
                         "Please track your deployment in {} and ensure the WEBSITE_RUN_FROM_PACKAGE app setting "
                         "is removed.".format(deployment_status_url))
  
      # check the status of async deployment
      response = _check_zip_deployment_status(cmd, resource_group_name, name, deployment_status_url,
                                              authorization, timeout)
      return response
  
  
  def _get_scm_url(cmd, resource_group_name, name, slot=None):
      from azure.mgmt.web.models import HostType
      webapp = show_webapp(cmd, resource_group_name, name, slot=slot)
      for host in webapp.host_name_ssl_states or []:
          if host.host_type == HostType.repository:
              return "https://{}".format(host.name)
  
      # this should not happen, but throw anyway
      raise ValueError('Failed to retrieve Scm Uri')
  
  
  def restart_webapp(cmd, resource_group_name, name, slot=None):
      return WebAppClient.restart(cmd=cmd, resource_group_name=resource_group_name, name=name, slot=slot)
  
  
  def _check_zip_deployment_status(cmd, rg_name, name, deployment_status_url, authorization, timeout=None):
      import requests
      from azure.cli.core.util import should_disable_connection_verify
      total_trials = (int(timeout) // 2) if timeout else 450
      num_trials = 0
      while num_trials < total_trials:
          time.sleep(2)
          response = requests.get(deployment_status_url, headers=authorization,
                                  verify=not should_disable_connection_verify())
          try:
              res_dict = response.json()
          except json.decoder.JSONDecodeError:
              logger.warning("Deployment status endpoint %s returns malformed data. Retrying...", deployment_status_url)
              res_dict = {}
          finally:
              num_trials = num_trials + 1
  
          if res_dict.get('status', 0) == 3:
              _configure_default_logging(cmd, rg_name, name)
              raise CLIError("""Zip deployment failed. {}. Please run the command az webapp log tail
                             -n {} -g {}""".format(res_dict, name, rg_name))
          if res_dict.get('status', 0) == 4:
              break
          if 'progress' in res_dict:
              logger.info(res_dict['progress'])  # show only in debug mode, customers seem to find this confusing
      # if the deployment is taking longer than expected
      if res_dict.get('status', 0) != 4:
          _configure_default_logging(cmd, rg_name, name)
          raise CLIError("""Timeout reached by the command, however, the deployment operation
                         is still on-going. Navigate to your scm site to check the deployment status""")
      return res_dict
  
  
  def _fill_ftp_publishing_url(cmd, webapp, resource_group_name, name, slot=None):
      profiles = list_publish_profiles(cmd, resource_group_name, name, slot)
      try:
          url = next((p['publishUrl'] for p in profiles if p['publishMethod'] == 'FTP'), None)
          setattr(webapp, 'ftpPublishingUrl', url)
      except StopIteration:
          pass
  
      return webapp
  
  
  def _update_host_name_ssl_state(cmd, resource_group_name, webapp_name, webapp,
                                  host_name, ssl_state, thumbprint, slot=None):
      from azure.mgmt.web.models import HostNameSslState
  
      webapp.host_name_ssl_states = [HostNameSslState(name=host_name,
                                                      ssl_state=ssl_state,
                                                      thumbprint=thumbprint,
                                                      to_update=True)]
  
      webapp_dict = webapp.serialize()
  
      if webapp.extended_location is not None:
          webapp_dict["extendedLocation"]["type"] = "customLocation"
  
      management_hostname = cmd.cli_ctx.cloud.endpoints.resource_manager
      api_version = "2020-12-01"
      sub_id = get_subscription_id(cmd.cli_ctx)
      if slot is None:
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/sites/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              webapp_name,
              api_version)
      else:
          url_fmt = "{}/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Web/sites/{}/slots/{}?api-version={}"
          request_url = url_fmt.format(
              management_hostname.strip('/'),
              sub_id,
              resource_group_name,
              webapp_name,
              slot,
              api_version)
  
      return send_raw_request(cmd.cli_ctx, "PUT", request_url, body=json.dumps(webapp_dict))
  
  
  def _match_host_names_from_cert(hostnames_from_cert, hostnames_in_webapp):
      # the goal is to match '*.foo.com' with host name like 'admin.foo.com', 'logs.foo.com', etc
      matched = set()
      for hostname in hostnames_from_cert:
          if hostname.startswith('*'):
              for h in hostnames_in_webapp:
                  if hostname[hostname.find('.'):] == h[h.find('.'):]:
                      matched.add(h)
          elif hostname in hostnames_in_webapp:
              matched.add(hostname)
      return matched
  
  
  def _update_ssl_binding(cmd, resource_group_name, name, certificate_thumbprint, ssl_type, slot=None):
      client = web_client_factory(cmd.cli_ctx, api_version="2021-01-01")
      webapp = client.web_apps.get(resource_group_name, name)
      if not webapp:
          raise ResourceNotFoundError("'{}' app doesn't exist".format(name))
  
      cert_resource_group_name = parse_resource_id(webapp.server_farm_id)['resource_group']
      webapp_certs = client.certificates.list_by_resource_group(cert_resource_group_name)
  
      found_cert = None
      for webapp_cert in webapp_certs:
          if webapp_cert.thumbprint == certificate_thumbprint:
              found_cert = webapp_cert
      if not found_cert:
          webapp_certs = client.certificates.list_by_resource_group(resource_group_name)
          for webapp_cert in webapp_certs:
              if webapp_cert.thumbprint == certificate_thumbprint:
                  found_cert = webapp_cert
      if found_cert:
          if len(found_cert.host_names) == 1 and not found_cert.host_names[0].startswith('*'):
              return _update_host_name_ssl_state(cmd, resource_group_name, name, webapp,
                                                 found_cert.host_names[0], ssl_type,
                                                 certificate_thumbprint, slot)
  
          query_result = list_hostnames(cmd, resource_group_name, name, slot)
          hostnames_in_webapp = [x.name.split('/')[-1] for x in query_result]
          to_update = _match_host_names_from_cert(found_cert.host_names, hostnames_in_webapp)
          for h in to_update:
              _update_host_name_ssl_state(cmd, resource_group_name, name, webapp,
                                          h, ssl_type, certificate_thumbprint, slot)
  
          return show_webapp(cmd, resource_group_name, name, slot)
  
      raise ResourceNotFoundError("Certificate for thumbprint '{}' not found.".format(certificate_thumbprint))
  
  
  def bind_ssl_cert(cmd, resource_group_name, name, certificate_thumbprint, ssl_type, slot=None):
      SslState = cmd.get_models('SslState')
      return _update_ssl_binding(cmd, resource_group_name, name, certificate_thumbprint,
                                 SslState.sni_enabled if ssl_type == 'SNI' else SslState.ip_based_enabled, slot)
  
  
  def unbind_ssl_cert(cmd, resource_group_name, name, certificate_thumbprint, slot=None):
      SslState = cmd.get_models('SslState')
      return _update_ssl_binding(cmd, resource_group_name, name,
                                 certificate_thumbprint, SslState.disabled, slot)
  