Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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']
Copy link
Contributor

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.

Copy link
Contributor Author

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

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)
Expand All @@ -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 plan 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')
Expand Down
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))



Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty line. Didn't pylint complain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently not or it would not have passed CI 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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()


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
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 parent like resource which contains the webapp, and sooner or later users will need to know the existence of the plan for scenarios like scale up/down. This is different with vnet, subnet, etc, which would be mostly transparent to users.

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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,78 @@ interactions:
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Type: [application/json; charset=utf-8]
User-Agent: [python/3.5.2 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.5
msrest_azure/0.4.7 websitemanagementclient/0.31.0 Azure-SDK-For-Python AZURECLI/TEST/2.0.0+dev]
User-Agent: [python/3.5.3 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.6
msrest_azure/0.4.7 resourcemanagementclient/0.30.2 Azure-SDK-For-Python
AZURECLI/TEST/2.0.1+dev]
accept-language: [en-US]
x-ms-client-request-id: [5b453146-05de-11e7-adce-480fcf502758]
x-ms-client-request-id: [554373c2-0b56-11e7-b694-64510658e3b3]
method: GET
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms/webapp-error-plan?api-version=2016-09-01
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/clitest-error2?api-version=2016-09-01
response:
body: {string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms/webapp-error-plan","name":"webapp-error-plan","type":"Microsoft.Web/serverfarms","kind":"app","location":"West
US","tags":null,"properties":{"serverFarmId":0,"name":"webapp-error-plan","workerSize":"Small","workerSizeId":0,"workerTierName":null,"numberOfWorkers":1,"currentWorkerSize":"Small","currentWorkerSizeId":0,"currentNumberOfWorkers":1,"status":"Ready","webSpace":"clitest-error2-WestUSwebspace","subscription":"8d57ddbd-c779-40ea-b660-1015f4bf027d","adminSiteName":null,"hostingEnvironment":null,"hostingEnvironmentProfile":null,"maximumNumberOfWorkers":3,"planName":"VirtualDedicatedPlan","adminRuntimeSiteName":null,"computeMode":"Shared","siteMode":null,"geoRegion":"West
US","perSiteScaling":false,"numberOfSites":0,"hostingEnvironmentId":null,"tags":null,"kind":"app","resourceGroup":"clitest-error2","reserved":false,"mdmId":"waws-prod-bay-045_13008","targetWorkerCount":0,"targetWorkerSizeId":0,"provisioningState":"Succeeded"},"sku":{"name":"B1","tier":"Basic","size":"B1","family":"B","capacity":1}}'}
body: {string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2","name":"clitest-error2","location":"westus","properties":{"provisioningState":"Succeeded"}}'}
headers:
Cache-Control: [no-cache]
Content-Type: [application/json; charset=utf-8]
Date: ['Fri, 17 Mar 2017 21:11:47 GMT']
Expires: ['-1']
Pragma: [no-cache]
Strict-Transport-Security: [max-age=31536000; includeSubDomains]
Vary: [Accept-Encoding]
content-length: ['181']
status: {code: 200, message: OK}
- request:
body: null
headers:
Accept: [application/json]
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Type: [application/json; charset=utf-8]
User-Agent: [python/3.5.3 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.6
msrest_azure/0.4.7 websitemanagementclient/0.31.0 Azure-SDK-For-Python AZURECLI/TEST/2.0.1+dev]
accept-language: [en-US]
x-ms-client-request-id: [556a35b4-0b56-11e7-beb7-64510658e3b3]
method: GET
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms?api-version=2016-09-01
response:
body: {string: '{"value":[],"nextLink":null,"id":null}'}
headers:
Cache-Control: [no-cache]
Content-Type: [application/json]
Date: ['Fri, 17 Mar 2017 21:11:48 GMT']
Expires: ['-1']
Pragma: [no-cache]
Server: [Microsoft-IIS/8.0]
Strict-Transport-Security: [max-age=31536000; includeSubDomains]
Transfer-Encoding: [chunked]
Vary: [Accept-Encoding]
X-AspNet-Version: [4.0.30319]
X-Powered-By: [ASP.NET]
content-length: ['38']
status: {code: 200, message: OK}
- request:
body: '{"sku": {"name": "S1", "tier": "STANDARD", "capacity": 1}, "properties":
{"name": "webapp-error-test123_plan", "perSiteScaling": false}, "location":
"westus"}'
headers:
Accept: [application/json]
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Length: ['158']
Content-Type: [application/json; charset=utf-8]
User-Agent: [python/3.5.3 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.6
msrest_azure/0.4.7 websitemanagementclient/0.31.0 Azure-SDK-For-Python AZURECLI/TEST/2.0.1+dev]
accept-language: [en-US]
x-ms-client-request-id: [55c3eef0-0b56-11e7-9c6d-64510658e3b3]
method: PUT
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms/webapp-error-test123_plan?api-version=2016-09-01
response:
body: {string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms/webapp-error-test123_plan","name":"webapp-error-test123_plan","type":"Microsoft.Web/serverfarms","kind":"app","location":"West
US","tags":null,"properties":{"serverFarmId":0,"name":"webapp-error-test123_plan","workerSize":"Small","workerSizeId":0,"workerTierName":null,"numberOfWorkers":1,"currentWorkerSize":"Small","currentWorkerSizeId":0,"currentNumberOfWorkers":1,"status":"Ready","webSpace":"clitest-error2-WestUSwebspace","subscription":"0b1f6471-1bf0-4dda-aec3-cb9272f09590","adminSiteName":null,"hostingEnvironment":null,"hostingEnvironmentProfile":null,"maximumNumberOfWorkers":10,"planName":"VirtualDedicatedPlan","adminRuntimeSiteName":null,"computeMode":"Shared","siteMode":null,"geoRegion":"West
US","perSiteScaling":false,"numberOfSites":0,"hostingEnvironmentId":null,"tags":null,"kind":"app","resourceGroup":"clitest-error2","reserved":false,"mdmId":"waws-prod-bay-061_8887","targetWorkerCount":0,"targetWorkerSizeId":0,"provisioningState":"Succeeded"},"sku":{"name":"S1","tier":"Standard","size":"S1","family":"S","capacity":1}}'}
headers:
Cache-Control: [no-cache]
Content-Type: [application/json]
Date: ['Fri, 10 Mar 2017 22:10:24 GMT']
Date: ['Fri, 17 Mar 2017 21:11:55 GMT']
Expires: ['-1']
Pragma: [no-cache]
Server: [Microsoft-IIS/8.0]
Expand All @@ -28,21 +86,23 @@ interactions:
Vary: [Accept-Encoding]
X-AspNet-Version: [4.0.30319]
X-Powered-By: [ASP.NET]
content-length: ['1142']
content-length: ['1169']
x-ms-ratelimit-remaining-subscription-writes: ['1199']
status: {code: 200, message: OK}
- request:
body: '{"location": "West US", "properties": {"microService": "false", "serverFarmId":
"webapp-error-plan", "scmSiteAlsoStopped": false, "reserved": false}}'
body: '{"properties": {"serverFarmId": "/subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590/resourceGroups/clitest-error2/providers/Microsoft.Web/serverfarms/webapp-error-test123_plan",
"scmSiteAlsoStopped": false, "microService": "false", "reserved": false}, "location":
"westus"}'
headers:
Accept: [application/json]
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
Content-Length: ['149']
Content-Length: ['274']
Content-Type: [application/json; charset=utf-8]
User-Agent: [python/3.5.2 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.5
msrest_azure/0.4.7 websitemanagementclient/0.31.0 Azure-SDK-For-Python AZURECLI/TEST/2.0.0+dev]
User-Agent: [python/3.5.3 (Windows-10-10.0.14393-SP0) requests/2.9.1 msrest/0.4.6
msrest_azure/0.4.7 websitemanagementclient/0.31.0 Azure-SDK-For-Python AZURECLI/TEST/2.0.1+dev]
accept-language: [en-US]
x-ms-client-request-id: [5c6d23a4-05de-11e7-97fd-480fcf502758]
x-ms-client-request-id: [5a89b28a-0b56-11e7-84af-64510658e3b3]
method: PUT
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clitest-error2/providers/Microsoft.Web/sites/webapp-error-test123?api-version=2016-08-01
response:
Expand All @@ -55,13 +115,13 @@ interactions:
Cache-Control: [no-cache]
Content-Length: ['484']
Content-Type: [application/json; charset=utf-8]
Date: ['Fri, 10 Mar 2017 22:10:26 GMT']
Date: ['Fri, 17 Mar 2017 21:11:58 GMT']
Expires: ['-1']
Pragma: [no-cache]
Server: [Microsoft-IIS/8.0]
Strict-Transport-Security: [max-age=31536000; includeSubDomains]
X-AspNet-Version: [4.0.30319]
X-Powered-By: [ASP.NET]
x-ms-ratelimit-remaining-subscription-writes: ['1198']
x-ms-ratelimit-remaining-subscription-writes: ['1199']
status: {code: 409, message: Conflict}
version: 1
Loading