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
1 change: 1 addition & 0 deletions src/command_modules/azure-cli-appservice/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release History
* webapp: fix a bug in `az webapp delete` when `--slot` is provided
* webapp: remove `--runtime-version` from `az webapp auth update` as it's not very public ready
* webapp: az webapp config set support for min_tls_version & https2.0
* webapp: az webapp create support for multicontainers

0.1.31
++++++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
'LoginWithMicrosoftAccount': BuiltInAuthenticationProvider.microsoft_account,
'LoginWithTwitter': BuiltInAuthenticationProvider.twitter}

MULTI_CONTAINER_TYPES = ['COMPOSE', 'KUBE']

# pylint: disable=too-many-statements


Expand Down Expand Up @@ -73,6 +75,8 @@ def load_arguments(self, _):
with self.argument_context('webapp create') as c:
c.argument('name', options_list=['--name', '-n'], help='name of the new webapp')
c.argument('startup_file', help="Linux only. The web's startup file")
c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="Linux only. Config file for multicontainer apps. (local or remote)")
c.argument('runtime', options_list=['--runtime', '-r'], help="canonicalized web runtime in the format of Framework|Version, e.g. \"PHP|5.6\". Use 'az webapp list-runtimes' for available list") # TODO ADD completer
c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan',
completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
Expand Down Expand Up @@ -199,6 +203,9 @@ def load_arguments(self, _):
c.argument('docker_registry_server_user', options_list=['--docker-registry-server-user', '-u'], help='the container registry server username')
c.argument('docker_registry_server_password', options_list=['--docker-registry-server-password', '-p'], help='the container registry server password')
c.argument('websites_enable_app_service_storage', options_list=['--enable-app-service-storage', '-t'], help='enables platform storage (custom container only)', arg_type=get_three_state_flag(return_label=True))
c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help='config type', arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
Copy link
Copy Markdown
Contributor

@yugangw-msft yugangw-msft Apr 28, 2018

Choose a reason for hiding this comment

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

is this linux only as well like the one used in webapp create? I suggested we could consolidate them to avoid dupes.

c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="config file for multicontainer apps")
c.argument('show_multicontainer_config', action='store_true', help='shows decoded config if a multicontainer config is set')

with self.argument_context('webapp config set') as c:
c.argument('remote_debugging_enabled', help='enable or disable remote debugging', arg_type=get_three_state_flag(return_label=True))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@

from __future__ import print_function
import threading

try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse # pylint: disable=import-error
from six.moves.urllib.request import urlopen # pylint: disable=import-error, ungrouped-imports
from binascii import hexlify
from os import urandom
import json
import ssl
import sys
import OpenSSL.crypto

from knack.prompting import prompt_pass, NoTTYException
Expand All @@ -30,9 +34,10 @@

from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands import LongRunningOperation
from azure.cli.core.util import in_cloud_console

from .vsts_cd_provider import VstsContinuousDeliveryProvider
from ._params import AUTH_TYPES
from ._params import AUTH_TYPES, MULTI_CONTAINER_TYPES
from ._client_factory import web_client_factory, ex_handler_factory
from ._appservice_utils import _generic_site_operation

Expand All @@ -47,7 +52,7 @@

def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_file=None,
deployment_container_image_name=None, deployment_source_url=None, deployment_source_branch='master',
deployment_local_git=None):
deployment_local_git=None, multicontainer_config_type=None, multicontainer_config_file=None):
if deployment_source_url and deployment_local_git:
raise CLIError('usage error: --deployment-source-url <url> | --deployment-local-git')
client = web_client_factory(cmd.cli_ctx)
Expand All @@ -66,8 +71,10 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi
helper = _StackRuntimeHelper(client, linux=is_linux)

if is_linux:
if runtime and deployment_container_image_name:
raise CLIError('usage error: --runtime | --deployment-container-image-name')
if not validate_linux_create_options(runtime, deployment_container_image_name,
multicontainer_config_type, multicontainer_config_file):
raise CLIError("usage error: --runtime | --deployment-container-image-name |"
" --multicontainer-config-type TYPE --multicontainer-config-file FILE")
if startup_file:
site_config.app_command_line = startup_file

Expand All @@ -77,16 +84,17 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi
if not match:
raise CLIError("Linux Runtime '{}' is not supported."
"Please invoke 'list-runtimes' to cross check".format(runtime))

elif deployment_container_image_name:
site_config.linux_fx_version = _format_linux_fx_version(deployment_container_image_name)
site_config.app_settings.append(NameValuePair("WEBSITES_ENABLE_APP_SERVICE_STORAGE", "false"))
else: # must specify runtime
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why you remove this? Now we won't report anything if none of the arguments are supplied

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

validate_linux_create_options() will catch this right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

okay

raise CLIError('usage error: must specify --runtime | --deployment-container-image-name') # pylint: disable=line-too-long
elif multicontainer_config_type and multicontainer_config_file:
encoded_config_file = _get_linux_multicontainer_encoded_config_from_file(multicontainer_config_file)
site_config.linux_fx_version = _format_linux_fx_version(encoded_config_file, multicontainer_config_type)

elif runtime: # windows webapp with runtime specified
if startup_file or deployment_container_image_name:
raise CLIError("usage error: --startup-file or --deployment-container-image-name is "
if any([startup_file, deployment_container_image_name, multicontainer_config_file, multicontainer_config_type]):
raise CLIError("usage error: --startup-file or --deployment-container-image-name or "
"--multicontainer-config-type and --multicontainer-config-file is "
"only appliable on linux webapp")
match = helper.resolve(runtime)
if not match:
Expand Down Expand Up @@ -117,6 +125,14 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi
return webapp


def validate_linux_create_options(runtime=None, deployment_container_image_name=None,
multicontainer_config_type=None, multicontainer_config_file=None):
if bool(multicontainer_config_type) != bool(multicontainer_config_file):
return False
opts = [runtime, deployment_container_image_name, multicontainer_config_type]
return len([x for x in opts if x]) == 1 # you can only specify one out the combinations


def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None, slot_settings=None):
if not settings and not slot_settings:
raise CLIError('Usage Error: --settings |--slot-settings')
Expand Down Expand Up @@ -378,12 +394,14 @@ def _fill_ftp_publishing_url(cmd, webapp, resource_group_name, name, slot=None):
return webapp


def _format_linux_fx_version(custom_image_name):
def _format_linux_fx_version(custom_image_name, container_config_type=None):
fx_version = custom_image_name.strip()
fx_version_lower = fx_version.lower()
# handles case of only spaces
if fx_version:
if not fx_version_lower.startswith('docker|'):
if container_config_type:
fx_version = '{}|{}'.format(container_config_type, custom_image_name)
elif not fx_version_lower.startswith('docker|'):
fx_version = '{}|{}'.format('DOCKER', custom_image_name)
else:
fx_version = ' '
Expand All @@ -404,6 +422,36 @@ def _get_linux_fx_version(cmd, resource_group_name, name, slot=None):
return site_config.linux_fx_version


def url_validator(url):
try:
result = urlparse(url)
return all([result.scheme, result.netloc, result.path])
except ValueError:
return False


def _get_linux_multicontainer_decoded_config(cmd, resource_group_name, name, slot=None):
from base64 import b64decode
linux_fx_version = _get_linux_fx_version(cmd, resource_group_name, name, slot)
if not any([linux_fx_version.startswith(s) for s in MULTI_CONTAINER_TYPES]):
raise CLIError("Cannot decode config that is not one of the"
" following types: {}".format(','.join(MULTI_CONTAINER_TYPES)))
return b64decode(linux_fx_version.split('|')[1].encode('utf-8'))


def _get_linux_multicontainer_encoded_config_from_file(file_name):
from base64 import b64encode
config_file_bytes = None
if url_validator(file_name):
response = urlopen(file_name, context=_ssl_context())
config_file_bytes = response.read()
else:
with open(file_name, 'rb') as f:
config_file_bytes = f.read()
# Decode base64 encoded byte array into string
return b64encode(config_file_bytes).decode('utf-8')


# for any modifications to the non-optional parameters, adjust the reflection logic accordingly
# in the method
def update_site_configs(cmd, resource_group_name, name, slot=None,
Expand Down Expand Up @@ -459,6 +507,16 @@ def delete_app_settings(cmd, resource_group_name, name, setting_names, slot=None
return _build_app_settings_output(result.properties, slot_cfg_names.app_setting_names)


def _ssl_context():
if sys.version_info < (3, 4) or (in_cloud_console() and sys.platform.system() == 'Windows'):
try:
return ssl.SSLContext(ssl.PROTOCOL_TLS) # added in python 2.7.13 and 3.6
except AttributeError:
return ssl.SSLContext(ssl.PROTOCOL_TLSv1)

return ssl.create_default_context()


def _build_app_settings_output(app_settings, slot_cfg_names):
slot_cfg_names = slot_cfg_names or []
return [{'name': p,
Expand Down Expand Up @@ -528,7 +586,7 @@ def delete_connection_strings(cmd, resource_group_name, name, setting_names, slo
def update_container_settings(cmd, resource_group_name, name, docker_registry_server_url=None,
docker_custom_image_name=None, docker_registry_server_user=None,
websites_enable_app_service_storage=None, docker_registry_server_password=None,
slot=None):
multicontainer_config_type=None, multicontainer_config_file=None, slot=None):
settings = []
if docker_registry_server_url is not None:
settings.append('DOCKER_REGISTRY_SERVER_URL=' + docker_registry_server_url)
Expand Down Expand Up @@ -556,6 +614,13 @@ def update_container_settings(cmd, resource_group_name, name, docker_registry_se
update_app_settings(cmd, resource_group_name, name, settings, slot)
settings = get_app_settings(cmd, resource_group_name, name, slot)

if multicontainer_config_file and multicontainer_config_type:
encoded_config_file = _get_linux_multicontainer_encoded_config_from_file(multicontainer_config_file)
linux_fx_version = _format_linux_fx_version(encoded_config_file, multicontainer_config_type)
update_site_configs(cmd, resource_group_name, name, linux_fx_version=linux_fx_version)
elif multicontainer_config_file or multicontainer_config_type:
logger.warning('Must change both settings --multicontainer-config-file FILE --multicontainer-config-type TYPE')

return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name, name, settings))


Expand Down Expand Up @@ -585,19 +650,25 @@ def delete_container_settings(cmd, resource_group_name, name, slot=None):
delete_app_settings(cmd, resource_group_name, name, CONTAINER_APPSETTING_NAMES, slot)


def show_container_settings(cmd, resource_group_name, name, slot=None):
def show_container_settings(cmd, resource_group_name, name, show_multicontainer_config=None, slot=None):
settings = get_app_settings(cmd, resource_group_name, name, slot)
return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name,
name, settings, slot))
return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name, name, settings,
show_multicontainer_config, slot))


def _filter_for_container_settings(cmd, resource_group_name, name, settings, slot=None):
def _filter_for_container_settings(cmd, resource_group_name, name, settings,
show_multicontainer_config=None, slot=None):
result = [x for x in settings if x['name'] in CONTAINER_APPSETTING_NAMES]
fx_version = _get_linux_fx_version(cmd, resource_group_name, name, slot).strip()
if fx_version:
added_image_name = {'name': 'DOCKER_CUSTOM_IMAGE_NAME',
'value': fx_version}
result.append(added_image_name)
if show_multicontainer_config:
decoded_value = _get_linux_multicontainer_decoded_config(cmd, resource_group_name, name, slot)
decoded_image_name = {'name': 'DOCKER_CUSTOM_IMAGE_NAME_DECODED',
'value': decoded_value}
result.append(decoded_image_name)
return result


Expand Down Expand Up @@ -1116,7 +1187,6 @@ def view_in_browser(cmd, resource_group_name, name, slot=None, logs=False):


def _open_page_in_browser(url):
import sys
if sys.platform.lower() == 'darwin':
# handle 2 things:
# a. On OSX sierra, 'python -m webbrowser -t <url>' emits out "execution error: <url> doesn't
Expand Down Expand Up @@ -1289,7 +1359,6 @@ def _get_site_credential(cli_ctx, resource_group_name, name, slot=None):


def _get_log(url, user_name, password, log_file=None):
import sys
import certifi
import urllib3
try:
Expand Down
Loading