Skip to content

Commit

Permalink
Adding webapp extension for quickstart command (#42)
Browse files Browse the repository at this point in the history
* Adding webapp extension plus the whl file

Updating resource client factory by adding def

Fixing build

adding blank line

More build fixes

fixing  pylint errors

Build errors

PYlint fix

More PYlint fixes

FW:

* Updating code owners for webapps

* addressing PR feedback

* adding return None

* PYlint fixes

* diabling too-long pylint

* FW:

* FW: Pylint

* FW: fixing an issue where passing location value was not working correctly

* fet default was failing when no default was set
  • Loading branch information
panchagnula authored and derekbekoe committed Feb 2, 2018
1 parent 6baf006 commit dbf5f18
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
/src/servicebus/ @v-ajnava

/src/eventhubs/ @v-ajnava

/src/webapps/ @panchagnula
36 changes: 36 additions & 0 deletions src/webapps/azext_webapps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# --------------------------------------------------------------------------------------------
# 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

# pylint: disable=unused-import

import azext_webapps._help


class WebappsExtCommandLoader(AzCommandsLoader):

def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
webapps_custom = CliCommandType(
operations_tmpl='azext_webapps.custom#{}')
super(WebappsExtCommandLoader, self).__init__(cli_ctx=cli_ctx,
custom_command_type=webapps_custom,
min_profile="2017-03-10-profile")

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 and deploy operation instead of executing it",
default=False, action='store_true')


COMMAND_LOADER_CLS = WebappsExtCommandLoader
16 changes: 16 additions & 0 deletions src/webapps/azext_webapps/_help.py
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
"""
3 changes: 3 additions & 0 deletions src/webapps/azext_webapps/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"azext.minCliCoreVersion": "2.0.24"
}
85 changes: 85 additions & 0 deletions src/webapps/azext_webapps/create_util.py
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 returns the supported versions
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
146 changes: 146 additions & 0 deletions src/webapps/azext_webapps/custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# --------------------------------------------------------------------------------------------
# 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()

# Resource group: check if default RG is set
default_rg = cmd.cli_ctx.config.get('defaults', 'group', fallback=None)
if (default_rg and check_resource_group_supports_linux(cmd, default_rg, location)):
rg = default_rg
rg_mssg = "[Using default Resource group]"
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",
"serverfarm" : "%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
""")
logger.warning(create_json)
return None

# create RG if the RG doesn't already exist
if not check_resource_group_exists(cmd, rg):
logger.warning("Creating Resource group '%s' ...", rg)
create_resource_group(cmd, rg, location)
logger.warning("Resource group creation complete")
else:
logger.warning("Resource group '%s' already exists.", rg)

# 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("App service plan creation complete")
else:
logger.warning("App service plan '%s' already exists.", asp)

# 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)
logger.warning("All done. %s", create_json)
return None
2 changes: 2 additions & 0 deletions src/webapps/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bdist_wheel]
universal=1
41 changes: 41 additions & 0 deletions src/webapps/setup.py
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.4"

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
)

0 comments on commit dbf5f18

Please sign in to comment.