-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Adding webapp extension for quickstart command #42
Changes from 1 commit
97883f2
2e3c7f9
2bf64ca
944664b
4135561
e6c5c7f
18627f0
c93166f
80a5373
d6b21c7
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 |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# 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 import AzCommandsLoader | ||
import azext_webapps._help # pylint: disable=unused-import | ||
|
||
|
||
class WebappsCommandLoader(AzCommandsLoader): | ||
|
||
def __init__(self, cli_ctx=None): | ||
from azure.cli.core.commands import CliCommandType | ||
webapps_custom = CliCommandType( | ||
operations_tmpl='azext_webapps.custom#{}') | ||
super(WebappsCommandLoader, self).__init__(cli_ctx=cli_ctx, | ||
custom_command_type=webapps_custom) | ||
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. Add |
||
|
||
def load_command_table(self, _): | ||
with self.command_group('webapp') as g: | ||
g.custom_command('quickstart', 'create_deploy_webapp') | ||
return self.command_table | ||
|
||
def load_arguments(self, _): | ||
with self.argument_context('webapp quickstart') as c: | ||
c.argument('name', options_list=['--name', '-n'], help='name of the new webapp') | ||
c.argument('dryrun', | ||
help="shows summary of the create operation instead of actually creating and deploying the app", | ||
default=False) | ||
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. Add |
||
|
||
|
||
COMMAND_LOADER_CLS = WebappsCommandLoader |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from knack.help_files import helps | ||
|
||
|
||
helps['webapp quickstart'] = """ | ||
type: command | ||
short-summary: Create and deploy a node web app | ||
examples: | ||
- name: Create a web app with the default configuration. | ||
text: > | ||
az webapp quickstart -n MyUniqueAppName | ||
""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"azext.minCliCoreVersion": "2.0.24" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
import os | ||
import zipfile | ||
from azure.cli.core.commands.client_factory import get_mgmt_service_client | ||
from azure.mgmt.resource.resources.models import ResourceGroup | ||
|
||
|
||
def _resource_client_factory(cli_ctx, **_): | ||
from azure.cli.core.profiles import ResourceType | ||
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES) | ||
|
||
|
||
def web_client_factory(cli_ctx, **_): | ||
from azure.mgmt.web import WebSiteManagementClient | ||
return get_mgmt_service_client(cli_ctx, WebSiteManagementClient) | ||
|
||
|
||
def zip_contents_from_dir(dirPath): | ||
relroot = os.path.abspath(os.path.join(dirPath, os.pardir)) | ||
path_and_file = os.path.splitdrive(dirPath)[1] | ||
file_val = os.path.split(path_and_file)[1] | ||
zip_file_path = relroot + "\\" + file_val + ".zip" | ||
abs_src = os.path.abspath(dirPath) | ||
with zipfile.ZipFile("{}".format(zip_file_path), "w", zipfile.ZIP_DEFLATED) as zf: | ||
for dirname, subdirs, files in os.walk(dirPath): | ||
# skip node_modules folder for Node apps, | ||
# since zip_deployment will perfom the build operation | ||
if 'node_modules' in subdirs: | ||
subdirs.remove('node_modules') | ||
for filename in files: | ||
absname = os.path.abspath(os.path.join(dirname, filename)) | ||
arcname = absname[len(abs_src) + 1:] | ||
zf.write(absname, arcname) | ||
return zip_file_path | ||
|
||
|
||
def is_node_application(path): | ||
# for node application, package.json should exisit in the application root dir | ||
# if this exists we pass the path of the file to read it contents & get version | ||
package_json_file = os.path.join(path, 'package.json') | ||
if os.path.isfile(package_json_file): | ||
return package_json_file | ||
return '' | ||
|
||
|
||
def get_node_runtime_version_toSet(): | ||
version_val = "8.0" | ||
# trunc_version = float(node_version[:3]) | ||
# TODO: call the list_runtimes once there is an API that returs the supported versions | ||
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. nit typo: |
||
return version_val | ||
|
||
|
||
def create_resource_group(cmd, rg_name, location): | ||
rcf = _resource_client_factory(cmd.cli_ctx) | ||
rg_params = ResourceGroup(location=location) | ||
return rcf.resource_groups.create_or_update(rg_name, rg_params) | ||
|
||
|
||
def check_resource_group_exists(cmd, rg_name): | ||
rcf = _resource_client_factory(cmd.cli_ctx) | ||
return rcf.resource_groups.check_existence(rg_name) | ||
|
||
|
||
def check_resource_group_supports_linux(cmd, rg_name, location): | ||
# get all appservice plans from RG | ||
client = web_client_factory(cmd.cli_ctx) | ||
plans = list(client.app_service_plans.list_by_resource_group(rg_name)) | ||
# filter by location & reserverd=false | ||
for item in plans: | ||
if item.location == location and not item.reserved: | ||
return False | ||
return True | ||
|
||
|
||
def check_if_asp_exists(cmd, rg_name, asp_name): | ||
# get all appservice plans from RG | ||
client = web_client_factory(cmd.cli_ctx) | ||
for item in list(client.app_service_plans.list_by_resource_group(rg_name)): | ||
if item.name == asp_name: | ||
return True | ||
return False |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from __future__ import print_function | ||
from knack.log import get_logger | ||
|
||
from azure.mgmt.web.models import (AppServicePlan, SkuDescription) | ||
|
||
from azure.cli.command_modules.appservice.custom import ( | ||
enable_zip_deploy, | ||
create_webapp, | ||
update_app_settings, | ||
_get_sku_name) | ||
|
||
from .create_util import ( | ||
zip_contents_from_dir, | ||
is_node_application, | ||
get_node_runtime_version_toSet, | ||
create_resource_group, | ||
check_resource_group_exists, | ||
check_resource_group_supports_linux, | ||
check_if_asp_exists, | ||
web_client_factory | ||
) | ||
|
||
logger = get_logger(__name__) | ||
|
||
# pylint:disable=no-member,too-many-lines,too-many-locals,too-many-statements | ||
|
||
|
||
def create_deploy_webapp(cmd, name, location=None, dryrun=False): | ||
import os | ||
import json | ||
|
||
client = web_client_factory(cmd.cli_ctx) | ||
sku = "S1" | ||
os_val = "Linux" | ||
language = "node" | ||
full_sku = _get_sku_name(sku) | ||
|
||
if location is None: | ||
locs = client.list_geo_regions(sku, True) | ||
available_locs = [] | ||
for loc in locs: | ||
available_locs.append(loc.geo_region_name) | ||
location = available_locs[0] | ||
|
||
# Remove spaces from the location string, incase the GeoRegion string is used | ||
loc_name = location.replace(" ", "") | ||
|
||
asp = "appsvc_asp_{}_{}".format(os_val, loc_name) | ||
rg = "appsvc_rg_{}_{}".format(os_val, loc_name) | ||
|
||
# the code to deploy is expected to be the current directory the command is running from | ||
src_dir = os.getcwd() | ||
|
||
# if dir is empty, show a message in dry run | ||
do_deployment = False if os.listdir(src_dir) == [] else True | ||
package_json_path = is_node_application(src_dir) | ||
|
||
str_no_contents_warn = "" | ||
if not do_deployment: | ||
str_no_contents_warn = "[Empty directory, no deployment will be triggered]" | ||
|
||
if package_json_path == '': | ||
node_version = "[No package.json file found in root directory, not a Node app?]" | ||
version_used_create = "8.0" | ||
else: | ||
with open(package_json_path) as data_file: | ||
data = json.load(data_file) | ||
node_version = data['version'] | ||
version_used_create = get_node_runtime_version_toSet() | ||
|
||
# ResourceGroup: check if default RG is set | ||
default_rg = cmd.cli_ctx.config.get('defaults', 'group') | ||
if (default_rg and check_resource_group_supports_linux(cmd, default_rg, location)): | ||
rg = default_rg | ||
rg_mssg = "[Using default ResourceGroup]" | ||
else: | ||
rg_mssg = "" | ||
|
||
runtime_version = "{}|{}".format(language, version_used_create) | ||
src_path = "{} {}".format(src_dir.replace("\\", "\\\\"), str_no_contents_warn) | ||
rg_str = "{} {}".format(rg, rg_mssg) | ||
|
||
dry_run_str = r""" { | ||
"name" : "%s", | ||
"appServicePlan" : "%s", | ||
"resourceGroup" : "%s", | ||
"sku": "%s", | ||
"os": "%s", | ||
"location" : "%s", | ||
"src_path" : "%s", | ||
"version_detected": "%s", | ||
"version_to_create": "%s" | ||
} | ||
""" % (name, asp, rg_str, full_sku, os_val, location, src_path, | ||
node_version, runtime_version) | ||
|
||
create_json = json.dumps(json.loads(dry_run_str), indent=4, sort_keys=True) | ||
if dryrun: | ||
logger.warning(""" | ||
Web app will be created with the below configuration, | ||
re-run command without the --dryrun flag to create & deploy a new app | ||
""") | ||
return logger.warning(create_json) | ||
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 do you need to return the logger warning?
|
||
|
||
# create RG if the RG doesn't already exist | ||
if not check_resource_group_exists(cmd, rg): | ||
logger.warning("Creating ResourceGroup '%s' ...", rg) | ||
create_resource_group(cmd, rg, location) | ||
logger.warning("ResourceGroup creation complete") | ||
else: | ||
logger.warning("Resource Group '%s' already exists.", rg) | ||
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. Would suggest |
||
|
||
# create asp | ||
if not check_if_asp_exists(cmd, rg, asp): | ||
logger.warning("Creating App service plan '%s' ...", asp) | ||
sku_def = SkuDescription(tier=full_sku, name=sku, capacity=1) | ||
plan_def = AppServicePlan(loc_name, app_service_plan_name=asp, | ||
sku=sku_def, reserved=True) | ||
client.app_service_plans.create_or_update(rg, asp, plan_def) | ||
logger.warning("AppServicePlan creation complete") | ||
else: | ||
logger.warning("AppServicePlan '%s' already exists.", asp) | ||
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. Same here |
||
|
||
# create the Linux app | ||
logger.warning("Creating app '%s' ....", name) | ||
create_webapp(cmd, rg, name, asp, runtime_version) | ||
logger.warning("Webapp creation complete") | ||
|
||
# setting to build after deployment | ||
logger.warning("Updating app settings to enable build after deployment") | ||
update_app_settings(cmd, rg, name, ["SCM_DO_BUILD_DURING_DEPLOYMENT=true"]) | ||
|
||
# zip contents & deploy | ||
logger.warning("Creating zip with contents of dir %s ...", src_dir) | ||
zip_file_path = zip_contents_from_dir(src_dir) | ||
|
||
logger.warning("Deploying and building contents to app. This operation can take some time to finish...") | ||
enable_zip_deploy(cmd, rg, name, zip_file_path) | ||
return logger.warning("All done. %s", create_json) | ||
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. You don't need to return with logger.warning. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[bdist_wheel] | ||
universal=1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#!/usr/bin/env python | ||
|
||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from codecs import open | ||
from setuptools import setup, find_packages | ||
|
||
VERSION = "0.0.1" | ||
|
||
CLASSIFIERS = [ | ||
'Development Status :: 4 - Beta', | ||
'Intended Audience :: Developers', | ||
'Intended Audience :: System Administrators', | ||
'Programming Language :: Python', | ||
'Programming Language :: Python :: 2', | ||
'Programming Language :: Python :: 2.7', | ||
'Programming Language :: Python :: 3', | ||
'Programming Language :: Python :: 3.4', | ||
'Programming Language :: Python :: 3.5', | ||
'Programming Language :: Python :: 3.6', | ||
'License :: OSI Approved :: MIT License', | ||
] | ||
|
||
DEPENDENCIES = [] | ||
|
||
setup( | ||
name='webapps', | ||
version=VERSION, | ||
description='An Azure CLI Extension to manage appservice resources', | ||
long_description='An Azure CLI Extension to manage appservice resources', | ||
license='MIT', | ||
author='Sisira Panchagnula', | ||
author_email='sisirap@microsoft.com', | ||
url='https://github.com/Azure/azure-cli-extensions', | ||
classifiers=CLASSIFIERS, | ||
packages=find_packages(exclude=["tests"]), | ||
install_requires=DEPENDENCIES | ||
) |
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.
Would recommend
WebappsExtCommandLoader
instead.The azure-cli-appservice module has the name
AppserviceCommandsLoader
and just want to make sure that the class names don't conflict in the future. By addingExt
, this should be clear enough.