-
Notifications
You must be signed in to change notification settings - Fork 3.3k
appservice: support to create plan when create a webapp #2550
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,16 +4,15 @@ | |
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| from argcomplete.completers import FilesCompleter | ||
| from azure.mgmt.web.models import DatabaseType | ||
|
|
||
| from azure.cli.core.commands import register_cli_argument | ||
|
|
||
| from azure.cli.core.commands.parameters import (resource_group_name_type, location_type, | ||
| get_resource_name_completion_list, file_type, | ||
| CliArgumentType, ignore_type, enum_choice_list) | ||
|
|
||
| from azure.mgmt.web.models import DatabaseType | ||
|
|
||
| from ._client_factory import web_client_factory | ||
| from azure.cli.command_modules.appservice._validators import process_webapp_create_namespace | ||
| from azure.cli.command_modules.appservice._client_factory import web_client_factory | ||
|
|
||
| def _generic_site_operation(resource_group_name, name, operation_name, slot=None, #pylint: disable=too-many-arguments | ||
| extra_parameter=None, client=None): | ||
|
|
@@ -39,8 +38,9 @@ def get_hostname_completion_list(prefix, action, parsed_args, **kwargs): # pylin | |
| #pylint: disable=line-too-long | ||
| # PARAMETER REGISTRATION | ||
| name_arg_type = CliArgumentType(options_list=('--name', '-n'), metavar='NAME') | ||
| sku_arg_type = CliArgumentType(help='The pricing tiers, e.g., F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1(Premium Small), etc', | ||
| **enum_choice_list(['F1', 'FREE', 'D1', 'SHARED', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3'])) | ||
| _SKU_HELP = 'The pricing tiers, e.g., F1(Free), D1(Shared), B1(Basic Small), B2(Basic Medium), B3(Basic Large), S1(Standard Small), P1(Premium Small), etc' | ||
| _SKU_LIST = ['F1', 'FREE', 'D1', 'SHARED', 'B1', 'B2', 'B3', 'S1', 'S2', 'S3', 'P1', 'P2', 'P3'] | ||
| sku_arg_type = CliArgumentType(help=_SKU_HELP, **enum_choice_list(_SKU_LIST)) | ||
|
|
||
| register_cli_argument('appservice', 'resource_group_name', arg_type=resource_group_name_type) | ||
| register_cli_argument('appservice', 'location', arg_type=location_type) | ||
|
|
@@ -60,9 +60,17 @@ def get_hostname_completion_list(prefix, action, parsed_args, **kwargs): # pylin | |
| register_cli_argument('appservice web', 'name', configured_default='web', | ||
| arg_type=name_arg_type, completer=get_resource_name_completion_list('Microsoft.Web/sites'), id_part='name', | ||
| help="name of the web. You can configure the default using 'az configure --defaults web=<name>'") | ||
| register_cli_argument('appservice web create', 'name', options_list=('--name', '-n'), help='name of the new webapp') | ||
| register_cli_argument('appservice web create', 'plan', options_list=('--plan', '-p'), completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'), | ||
| help="name or resource id of the app service plan. Use 'appservice plan create' to get one") | ||
| register_cli_argument('appservice web create', 'name', options_list=('--name', '-n'), help='name of the new webapp', validator=process_webapp_create_namespace) | ||
| register_cli_argument('appservice web create', 'create_plan', ignore_type) | ||
| register_cli_argument('appservice web create', 'plan', arg_group='AppService Plan', | ||
| options_list=('--plan', '-p'), completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'), | ||
| help='Appservice plan name. Can also reference an existing subnet by ID. If omitted, an appropriate plan in the same resource group will be selected automatically, or a new one will be created.') | ||
|
||
| register_cli_argument('appservice web create', 'sku', arg_group='AppService Plan', | ||
| help='{}. Default: S1'.format(_SKU_HELP), **enum_choice_list(_SKU_LIST)) | ||
| register_cli_argument('appservice web create', 'number_of_workers', help='Number of workers to be allocated. Default: 1', type=int, arg_group='AppService Plan') | ||
| register_cli_argument('appservice web create', 'is_linux', action='store_true', help='create a new linux web') | ||
| register_cli_argument('appservice web create', 'location', location_type, help='Location in which to create webapp and related resources. Defaults to the resource group\'s location.') | ||
|
|
||
| register_cli_argument('appservice web browse', 'logs', options_list=('--logs', '-l'), action='store_true', help='Enable viewing the log stream immediately after launching the web app') | ||
|
|
||
| register_cli_argument('appservice web deployment user', 'user_name', help='user name') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # -------------------------------------------------------------------------------------------- | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for license information. | ||
| # -------------------------------------------------------------------------------------------- | ||
|
|
||
| from azure.cli.core._util import CLIError | ||
| from azure.cli.core.commands.validators import get_default_location_from_resource_group | ||
|
|
||
| def _validate_plan_arg(namespace): | ||
|
||
| from ._client_factory import web_client_factory | ||
| namespace.create_plan = False | ||
| if namespace.plan: | ||
| from azure.cli.core.commands.arm import is_valid_resource_id | ||
| if not is_valid_resource_id(namespace.plan): | ||
| from msrestazure.azure_exceptions import CloudError | ||
| client = web_client_factory() | ||
| try: | ||
| client.app_service_plans.get(namespace.resource_group_name, namespace.plan) | ||
| except CloudError: | ||
| namespace.create_plan = True | ||
| else: | ||
| client = web_client_factory() | ||
| result = client.app_service_plans.list_by_resource_group(namespace.resource_group_name) | ||
| existing_plan = next((x for x in result if _match_plan_location(x, namespace.location) and | ||
| namespace.is_linux == (x.kind == 'linux')), None) | ||
| if existing_plan: | ||
| namespace.plan = existing_plan.id | ||
| else: | ||
| namespace.create_plan = True | ||
| namespace.plan = namespace.name + '_plan' | ||
|
|
||
| if not namespace.create_plan and (namespace.sku or namespace.number_of_workers): | ||
| raise CLIError('Usage error: argument values for --sku or --number-of-workers will ' | ||
| 'be ignored, as the new web will be created using an existing ' | ||
| 'plan {}. Please use --plan to specify a new plan'.format(namespace.plan)) | ||
|
|
||
|
|
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra empty line. Didn't pylint complain?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently not or it would not have passed CI 😁
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will not be a pylint error, rather PEP 8 style check should catch it. I have opened #2562 to get that done. |
||
| def _match_plan_location(plan, location): | ||
| # the problem with appservice is it uses display name, rather canonical name | ||
| # so we have to hack it | ||
| return plan.location.replace(' ', '').lower() == location.lower() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this because of a bug?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say, not a bug, but another anti-pattern in webapp's service. |
||
|
|
||
|
|
||
| def process_webapp_create_namespace(namespace): | ||
| get_default_location_from_resource_group(namespace) | ||
| _validate_plan_arg(namespace) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ | |
| RestoreRequest, FrequencyUnit, Certificate, HostNameSslState) | ||
|
|
||
| from azure.cli.core.commands.client_factory import get_mgmt_service_client | ||
| from azure.cli.core.commands.arm import is_valid_resource_id, parse_resource_id | ||
| from azure.cli.core.commands.arm import parse_resource_id | ||
| from azure.cli.core.commands import LongRunningOperation | ||
|
|
||
| from azure.cli.core.prompting import prompt_pass, NoTTYException | ||
|
|
@@ -56,18 +56,25 @@ def _get_detail_error(self, ex): | |
| "azure/app-service-web/app-service-linux-intro") | ||
| elif 'Not enough available reserved instance servers to satisfy' in detail: | ||
| detail = ("Plan with Linux worker can only be created in a group " + | ||
| "which has never contained a Windows worker. Please use " + | ||
| "a new resource group. Original error:" + detail) | ||
| "which has never contained a Windows worker, and vice versa. " + | ||
| "Please use a new resource group. Original error:" + detail) | ||
| return CLIError(detail) | ||
| except: #pylint: disable=bare-except | ||
| return ex | ||
|
|
||
| def create_webapp(resource_group_name, name, plan): | ||
| def create_webapp(resource_group_name, name, | ||
| plan=None, create_plan=False, | ||
| sku=None, is_linux=False, number_of_workers=None, | ||
| location=None): | ||
| client = web_client_factory() | ||
| if is_valid_resource_id(plan): | ||
| plan = parse_resource_id(plan)['name'] | ||
| location = _get_location_from_app_service_plan(client, resource_group_name, plan) | ||
| webapp_def = Site(server_farm_id=plan, location=location) | ||
| plan_id = plan | ||
| if create_plan: | ||
| logger.warning("Create appservice plan: '%s'", plan) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we issuing a warning? We don't do this for other creates (VM/VMSS/LB/AG) that create extra resources.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about it before put in a warning. The situation here is a bit different, that we are creating a |
||
| result = create_app_service_plan(resource_group_name, plan, is_linux, | ||
| sku or 'S1', number_of_workers or 1, location) | ||
| plan_id = result.id | ||
|
|
||
| webapp_def = Site(server_farm_id=plan_id, location=location) | ||
| poller = client.web_apps.create_or_update(resource_group_name, name, webapp_def) | ||
| return AppServiceLongRunningOperation()(poller) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get this metadata from SDK? It doesn't feel right for SDK to hard-code service options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, it is not right. But it is a known webapp api pattern, that offers no enum or choices. Yep, this makes lives a bit harder for client apps