Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[appservice-kube] Make compatible with new SDK #4494

Merged
merged 3 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/appservice-kube/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Release History
===============

0.1.5
++++++
* SSL bind bug fix
* Fix compatibility issue with CLI version 2.34.1

0.1.4
++++++
* Ensure compatibility of 'az webapp create' and 'az functionapp create' with CLI version 2.34.0
Expand Down
3 changes: 0 additions & 3 deletions src/appservice-kube/azext_appservice_kube/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import os


KUBE_DEFAULT_SKU = "K1"
KUBE_ASP_KIND = "linux,kubernetes"
KUBE_APP_KIND = "linux,kubernetes,app"
Expand Down
13 changes: 5 additions & 8 deletions src/appservice-kube/azext_appservice_kube/_create_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@ def get_site_availability(cmd, name):


def get_app_details(cmd, name, resource_group):
from azure.core.exceptions import ResourceNotFoundError as E
client = web_client_factory(cmd.cli_ctx)
return client.web_apps.get(resource_group, name)
# '''
# data = (list(filter(lambda x: name.lower() in x.name.lower(), client.web_apps.list())))
# _num_items = len(data)
# if _num_items > 0:
# return data[0]
# return None
# '''
try:
return client.web_apps.get(resource_group, name)
except E:
return None


# Portal uses the current_stack property in the app metadata to display the correct stack
Expand Down
12 changes: 12 additions & 0 deletions src/appservice-kube/azext_appservice_kube/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,15 @@
text: >
az webapp create -g MyResourceGroup -p MyPlan -n MyUniqueAppName --custom-location custom_location_name
"""

helps['webapp update'] = """
type: command
short-summary: Update an existing web app.
examples:
- name: Update the tags of a web app.
text: >
az webapp update -g MyResourceGroup -n MyAppName --set tags.tagName=tagValue
- name: Update a web app. (autogenerated)
text: az webapp update --https-only true --name MyAppName --resource-group MyResourceGroup
crafted: true
"""
8 changes: 8 additions & 0 deletions src/appservice-kube/azext_appservice_kube/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def load_arguments(self, _):
completer=get_resource_name_completion_list('Microsoft.Web/sites'), id_part='name',
help="name of the web app. You can configure the default using `az configure --defaults web=<name>`")

with self.argument_context('webapp update') as c:
c.argument('client_affinity_enabled', help="Enables sending session affinity cookies.",
arg_type=get_three_state_flag(return_label=True))
c.argument('https_only', help="Redirect all traffic made to an app using HTTP to HTTPS.",
arg_type=get_three_state_flag(return_label=True))
c.argument('minimum_elastic_instance_count', options_list=["--minimum-elastic-instance-count", "-i"], type=int, is_preview=True, help="Minimum number of instances. App must be in an elastic scale App Service Plan.")
c.argument('prewarmed_instance_count', options_list=["--prewarmed-instance-count", "-w"], type=int, is_preview=True, help="Number of preWarmed instances. App must be in an elastic scale App Service Plan.")

with self.argument_context('webapp create') as c:
c.argument('name', options_list=['--name', '-n'], help='name of the new web app', validator=validate_site_create)
c.argument('custom_location', help="Name or ID of the custom location. Use an ID for a custom location in a different resource group from the app")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"azext.isPreview": true,
"azext.minCliCoreVersion": "2.34.0"
"azext.minCliCoreVersion": "2.34.1"
}
4 changes: 4 additions & 0 deletions src/appservice-kube/azext_appservice_kube/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def load_command_table(self, _):
client_factory=cf_plans
)

appservice_kube_custom = CliCommandType(operations_tmpl='azext_appservice_kube.custom#{}')

with self.command_group('appservice kube', is_preview=True) as g:
g.custom_show_command('show', 'show_kube_environments')
g.custom_wait_command('wait', 'show_kube_environments')
Expand All @@ -70,6 +72,8 @@ def load_command_table(self, _):
g.custom_show_command('show', 'show_webapp', table_transformer=transform_web_output)
g.custom_command('scale', 'scale_webapp')
g.custom_command('restart', 'restart_webapp')
g.generic_update_command('update', getter_name='get_webapp', setter_name='set_webapp',
custom_func_name='update_webapp', command_type=appservice_kube_custom)

with self.command_group('webapp config ssl') as g:
g.custom_command('bind', 'bind_ssl_cert')
Expand Down
105 changes: 89 additions & 16 deletions src/appservice-kube/azext_appservice_kube/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
update_container_settings,
_rename_server_farm_props,
get_site_configs,
get_webapp,
_get_site_credential,
_format_fx_version,
_get_extension_version_functionapp,
Expand Down Expand Up @@ -46,9 +45,10 @@
update_app_settings,
list_hostnames,
_convert_camel_to_snake_case,
_get_content_share_name)
_get_content_share_name,
get_app_service_plan_from_webapp)
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.command_modules.appservice.utils import retryable_method, get_sku_tier
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
Expand All @@ -70,7 +70,7 @@

logger = get_logger(__name__)

# pylint: disable=too-many-locals,too-many-lines
# pylint: disable=too-many-locals,too-many-lines,consider-using-f-string


# TODO remove and replace with calls to KubeEnvironmentsOperations once the SDK gets updated
Expand Down Expand Up @@ -472,7 +472,9 @@ def _get_kube_env_from_custom_location(cmd, custom_location, resource_group):
if kube.extended_location and kube.extended_location.type == "CustomLocation":
parsed_custom_location_2 = parse_resource_id(kube.extended_location.name)

if parsed_custom_location_2["name"].lower() == custom_location_name.lower() and parsed_custom_location_2.get("resource_group").lower() == resource_group.lower():
matched_name = parsed_custom_location_2["name"].lower() == custom_location_name.lower()
matched_rg = parsed_custom_location_2.get("resource_group").lower() == resource_group.lower()
if matched_name and matched_rg:
kube_environment_id = kube.id
break

Expand Down Expand Up @@ -515,17 +517,20 @@ def _get_custom_location_id_from_kube_env(kube):
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
appservice_plan_json["properties"]["kubeEnvironmentProfile"] = kube_env.serialize()

if appservice_plan_json.get("extendedLocation") is None and extended_location is not None:
appservice_plan_json["extendedLocation"] = extended_location
appservice_plan_json["extendedLocation"] = extended_location.serialize()

appservice_plan_json['type'] = 'Microsoft.Web/serverfarms'
if appservice_plan_json.get("extendedLocation") is not None:
appservice_plan_json["extendedLocation"]["type"] = "CustomLocation"


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')
HostingEnvironmentProfile, SkuDescription, AppServicePlan, ExtendedLocation, KubeEnvironmentProfile = cmd.get_models('HostingEnvironmentProfile', 'SkuDescription', 'AppServicePlan', 'ExtendedLocation', 'KubeEnvironmentProfile') # pylint: disable=line-too-long

sku = _normalize_sku(sku)
_validate_asp_sku(app_service_environment, custom_location, sku)
Expand Down Expand Up @@ -561,16 +566,15 @@ def create_app_service_plan_inner(cmd, resource_group_name, name, is_linux, hype
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}
kube_def = KubeEnvironmentProfile(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"}
extended_location_envelope = ExtendedLocation(name=_get_custom_location_id_from_kube_env(kube_env),
type="CustomLocation")

if kube_env is not None:
location = kube_env["location"]
Expand Down Expand Up @@ -897,6 +901,74 @@ def create_webapp(cmd, resource_group_name, name, plan=None, runtime=None, custo
return webapp


def update_webapp(cmd, instance, client_affinity_enabled=None, https_only=None, minimum_elastic_instance_count=None,
prewarmed_instance_count=None):
if 'function' in instance.kind:
raise ValidationError("please use 'az functionapp update' to update this function app")
if minimum_elastic_instance_count or prewarmed_instance_count:
args = ["--minimum-elastic-instance-count", "--prewarmed-instance-count"]
plan = get_app_service_plan_from_webapp(cmd, instance)
sku = _normalize_sku(plan.sku.name)
if get_sku_tier(sku) not in ["PREMIUMV2", "PREMIUMV3"]:
raise ValidationError("{} are only supported for elastic premium V2/V3 SKUs".format(str(args)))
if not plan.elastic_scale_enabled:
raise ValidationError("Elastic scale is not enabled on the App Service Plan. Please update the plan ")
if (minimum_elastic_instance_count or 0) > plan.maximum_elastic_worker_count:
raise ValidationError("--minimum-elastic-instance-count: Minimum elastic instance count is greater than "
"the app service plan's maximum Elastic worker count. "
"Please choose a lower count or update the plan's maximum ")
if (prewarmed_instance_count or 0) > plan.maximum_elastic_worker_count:
raise ValidationError("--prewarmed-instance-count: Prewarmed instance count is greater than "
"the app service plan's maximum Elastic worker count. "
"Please choose a lower count or update the plan's maximum ")
from azure.mgmt.web.models import SkuDescription

if client_affinity_enabled is not None:
instance.client_affinity_enabled = client_affinity_enabled == 'true'
if https_only is not None:
instance.https_only = https_only == 'true'

if minimum_elastic_instance_count is not None:
from azure.mgmt.web.models import SiteConfig
# Need to create a new SiteConfig object to ensure that the new property is included in request body
conf = SiteConfig(**instance.site_config.as_dict())
conf.minimum_elastic_instance_count = minimum_elastic_instance_count
instance.site_config = conf

if prewarmed_instance_count is not None:
instance.site_config.pre_warmed_instance_count = prewarmed_instance_count

client = web_client_factory(cmd.cli_ctx)
plan_parsed = parse_resource_id(instance.server_farm_id)
plan_info = client.app_service_plans.get(plan_parsed['resource_group'], plan_parsed['name'])

is_kube = _is_webapp_kube(instance.extended_location, plan_info, SkuDescription)
has_custom_location_id = instance.extended_location and is_valid_resource_id(instance.extended_location.name)
if is_kube and has_custom_location_id:
custom_location_id = instance.extended_location.name
instance.enable_additional_properties_sending()
extended_loc = {'name': custom_location_id, 'type': 'CustomLocation'}
instance.additional_properties["extendedLocation"] = extended_loc

return instance


# for generic updater
def get_webapp(cmd, resource_group_name, name, slot=None):
return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get', slot)


def set_webapp(cmd, resource_group_name, name, slot=None, **kwargs): # pylint: disable=unused-argument
instance = kwargs['parameters']
client = web_client_factory(cmd.cli_ctx)
updater = client.web_apps.begin_create_or_update_slot if slot else client.web_apps.begin_create_or_update
kwargs = dict(resource_group_name=resource_group_name, name=name, site_envelope=instance)
if slot:
kwargs['slot'] = slot

return updater(**kwargs)


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)
Expand Down Expand Up @@ -1727,9 +1799,10 @@ def _update_ssl_binding(cmd, resource_group_name, name, 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)
_update_host_name_ssl_state(cmd, resource_group_name, name, webapp,
found_cert.host_names[0], ssl_type,
certificate_thumbprint, slot)
return show_webapp(cmd, resource_group_name, name, slot)

query_result = list_hostnames(cmd, resource_group_name, name, slot)
hostnames_in_webapp = [x.name.split('/')[-1] for x in query_result]
Expand Down
Loading