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

Adding webapp extension for quickstart command #42

Merged
merged 10 commits into from
Feb 2, 2018
Merged
Show file tree
Hide file tree
Changes from 6 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
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
33 changes: 33 additions & 0 deletions src/webapps/azext_webapps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# --------------------------------------------------------------------------------------------
# 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

# pylint: disable=line-too-long


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 operation instead of actually creating and deploying the app",
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')
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.2"

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='[email protected]',
url='https://github.com/Azure/azure-cli-extensions',
classifiers=CLASSIFIERS,
packages=find_packages(exclude=["tests"]),
install_requires=DEPENDENCIES
)