Skip to content

Commit c7641a4

Browse files
Lukasz Stempniewiczyugangw-msft
authored andcommitted
Websites: Add Multicontainer Capabilities (#6255)
1 parent 18067c5 commit c7641a4

18 files changed

Lines changed: 2291 additions & 1323 deletions

src/command_modules/azure-cli-appservice/HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Release History
77
* webapp: fix a bug in `az webapp delete` when `--slot` is provided
88
* webapp: remove `--runtime-version` from `az webapp auth update` as it's not very public ready
99
* webapp: az webapp config set support for min_tls_version & https2.0
10+
* webapp: az webapp create support for multicontainers
1011

1112
0.1.31
1213
++++++

src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/_params.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
'LoginWithMicrosoftAccount': BuiltInAuthenticationProvider.microsoft_account,
2323
'LoginWithTwitter': BuiltInAuthenticationProvider.twitter}
2424

25+
MULTI_CONTAINER_TYPES = ['COMPOSE', 'KUBE']
26+
2527
# pylint: disable=too-many-statements
2628

2729

@@ -73,6 +75,8 @@ def load_arguments(self, _):
7375
with self.argument_context('webapp create') as c:
7476
c.argument('name', options_list=['--name', '-n'], help='name of the new webapp')
7577
c.argument('startup_file', help="Linux only. The web's startup file")
78+
c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help="Linux only.", arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
79+
c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="Linux only. Config file for multicontainer apps. (local or remote)")
7680
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
7781
c.argument('plan', options_list=['--plan', '-p'], configured_default='appserviceplan',
7882
completer=get_resource_name_completion_list('Microsoft.Web/serverFarms'),
@@ -199,6 +203,9 @@ def load_arguments(self, _):
199203
c.argument('docker_registry_server_user', options_list=['--docker-registry-server-user', '-u'], help='the container registry server username')
200204
c.argument('docker_registry_server_password', options_list=['--docker-registry-server-password', '-p'], help='the container registry server password')
201205
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))
206+
c.argument('multicontainer_config_type', options_list=['--multicontainer-config-type'], help='config type', arg_type=get_enum_type(MULTI_CONTAINER_TYPES))
207+
c.argument('multicontainer_config_file', options_list=['--multicontainer-config-file'], help="config file for multicontainer apps")
208+
c.argument('show_multicontainer_config', action='store_true', help='shows decoded config if a multicontainer config is set')
202209

203210
with self.argument_context('webapp config set') as c:
204211
c.argument('remote_debugging_enabled', help='enable or disable remote debugging', arg_type=get_three_state_flag(return_label=True))

src/command_modules/azure-cli-appservice/azure/cli/command_modules/appservice/custom.py

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55

66
from __future__ import print_function
77
import threading
8+
89
try:
910
from urllib.parse import urlparse
1011
except ImportError:
1112
from urlparse import urlparse # pylint: disable=import-error
13+
from six.moves.urllib.request import urlopen # pylint: disable=import-error, ungrouped-imports
1214
from binascii import hexlify
1315
from os import urandom
1416
import json
17+
import ssl
18+
import sys
1519
import OpenSSL.crypto
1620

1721
from knack.prompting import prompt_pass, NoTTYException
@@ -30,9 +34,10 @@
3034

3135
from azure.cli.core.commands.client_factory import get_mgmt_service_client
3236
from azure.cli.core.commands import LongRunningOperation
37+
from azure.cli.core.util import in_cloud_console
3338

3439
from .vsts_cd_provider import VstsContinuousDeliveryProvider
35-
from ._params import AUTH_TYPES
40+
from ._params import AUTH_TYPES, MULTI_CONTAINER_TYPES
3641
from ._client_factory import web_client_factory, ex_handler_factory
3742
from ._appservice_utils import _generic_site_operation
3843

@@ -47,7 +52,7 @@
4752

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

6873
if is_linux:
69-
if runtime and deployment_container_image_name:
70-
raise CLIError('usage error: --runtime | --deployment-container-image-name')
74+
if not validate_linux_create_options(runtime, deployment_container_image_name,
75+
multicontainer_config_type, multicontainer_config_file):
76+
raise CLIError("usage error: --runtime | --deployment-container-image-name |"
77+
" --multicontainer-config-type TYPE --multicontainer-config-file FILE")
7178
if startup_file:
7279
site_config.app_command_line = startup_file
7380

@@ -77,16 +84,17 @@ def create_webapp(cmd, resource_group_name, name, plan, runtime=None, startup_fi
7784
if not match:
7885
raise CLIError("Linux Runtime '{}' is not supported."
7986
"Please invoke 'list-runtimes' to cross check".format(runtime))
80-
8187
elif deployment_container_image_name:
8288
site_config.linux_fx_version = _format_linux_fx_version(deployment_container_image_name)
8389
site_config.app_settings.append(NameValuePair("WEBSITES_ENABLE_APP_SERVICE_STORAGE", "false"))
84-
else: # must specify runtime
85-
raise CLIError('usage error: must specify --runtime | --deployment-container-image-name') # pylint: disable=line-too-long
90+
elif multicontainer_config_type and multicontainer_config_file:
91+
encoded_config_file = _get_linux_multicontainer_encoded_config_from_file(multicontainer_config_file)
92+
site_config.linux_fx_version = _format_linux_fx_version(encoded_config_file, multicontainer_config_type)
8693

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

119127

128+
def validate_linux_create_options(runtime=None, deployment_container_image_name=None,
129+
multicontainer_config_type=None, multicontainer_config_file=None):
130+
if bool(multicontainer_config_type) != bool(multicontainer_config_file):
131+
return False
132+
opts = [runtime, deployment_container_image_name, multicontainer_config_type]
133+
return len([x for x in opts if x]) == 1 # you can only specify one out the combinations
134+
135+
120136
def update_app_settings(cmd, resource_group_name, name, settings=None, slot=None, slot_settings=None):
121137
if not settings and not slot_settings:
122138
raise CLIError('Usage Error: --settings |--slot-settings')
@@ -378,12 +394,14 @@ def _fill_ftp_publishing_url(cmd, webapp, resource_group_name, name, slot=None):
378394
return webapp
379395

380396

381-
def _format_linux_fx_version(custom_image_name):
397+
def _format_linux_fx_version(custom_image_name, container_config_type=None):
382398
fx_version = custom_image_name.strip()
383399
fx_version_lower = fx_version.lower()
384400
# handles case of only spaces
385401
if fx_version:
386-
if not fx_version_lower.startswith('docker|'):
402+
if container_config_type:
403+
fx_version = '{}|{}'.format(container_config_type, custom_image_name)
404+
elif not fx_version_lower.startswith('docker|'):
387405
fx_version = '{}|{}'.format('DOCKER', custom_image_name)
388406
else:
389407
fx_version = ' '
@@ -404,6 +422,36 @@ def _get_linux_fx_version(cmd, resource_group_name, name, slot=None):
404422
return site_config.linux_fx_version
405423

406424

425+
def url_validator(url):
426+
try:
427+
result = urlparse(url)
428+
return all([result.scheme, result.netloc, result.path])
429+
except ValueError:
430+
return False
431+
432+
433+
def _get_linux_multicontainer_decoded_config(cmd, resource_group_name, name, slot=None):
434+
from base64 import b64decode
435+
linux_fx_version = _get_linux_fx_version(cmd, resource_group_name, name, slot)
436+
if not any([linux_fx_version.startswith(s) for s in MULTI_CONTAINER_TYPES]):
437+
raise CLIError("Cannot decode config that is not one of the"
438+
" following types: {}".format(','.join(MULTI_CONTAINER_TYPES)))
439+
return b64decode(linux_fx_version.split('|')[1].encode('utf-8'))
440+
441+
442+
def _get_linux_multicontainer_encoded_config_from_file(file_name):
443+
from base64 import b64encode
444+
config_file_bytes = None
445+
if url_validator(file_name):
446+
response = urlopen(file_name, context=_ssl_context())
447+
config_file_bytes = response.read()
448+
else:
449+
with open(file_name, 'rb') as f:
450+
config_file_bytes = f.read()
451+
# Decode base64 encoded byte array into string
452+
return b64encode(config_file_bytes).decode('utf-8')
453+
454+
407455
# for any modifications to the non-optional parameters, adjust the reflection logic accordingly
408456
# in the method
409457
def update_site_configs(cmd, resource_group_name, name, slot=None,
@@ -459,6 +507,16 @@ def delete_app_settings(cmd, resource_group_name, name, setting_names, slot=None
459507
return _build_app_settings_output(result.properties, slot_cfg_names.app_setting_names)
460508

461509

510+
def _ssl_context():
511+
if sys.version_info < (3, 4) or (in_cloud_console() and sys.platform.system() == 'Windows'):
512+
try:
513+
return ssl.SSLContext(ssl.PROTOCOL_TLS) # added in python 2.7.13 and 3.6
514+
except AttributeError:
515+
return ssl.SSLContext(ssl.PROTOCOL_TLSv1)
516+
517+
return ssl.create_default_context()
518+
519+
462520
def _build_app_settings_output(app_settings, slot_cfg_names):
463521
slot_cfg_names = slot_cfg_names or []
464522
return [{'name': p,
@@ -528,7 +586,7 @@ def delete_connection_strings(cmd, resource_group_name, name, setting_names, slo
528586
def update_container_settings(cmd, resource_group_name, name, docker_registry_server_url=None,
529587
docker_custom_image_name=None, docker_registry_server_user=None,
530588
websites_enable_app_service_storage=None, docker_registry_server_password=None,
531-
slot=None):
589+
multicontainer_config_type=None, multicontainer_config_file=None, slot=None):
532590
settings = []
533591
if docker_registry_server_url is not None:
534592
settings.append('DOCKER_REGISTRY_SERVER_URL=' + docker_registry_server_url)
@@ -556,6 +614,13 @@ def update_container_settings(cmd, resource_group_name, name, docker_registry_se
556614
update_app_settings(cmd, resource_group_name, name, settings, slot)
557615
settings = get_app_settings(cmd, resource_group_name, name, slot)
558616

617+
if multicontainer_config_file and multicontainer_config_type:
618+
encoded_config_file = _get_linux_multicontainer_encoded_config_from_file(multicontainer_config_file)
619+
linux_fx_version = _format_linux_fx_version(encoded_config_file, multicontainer_config_type)
620+
update_site_configs(cmd, resource_group_name, name, linux_fx_version=linux_fx_version)
621+
elif multicontainer_config_file or multicontainer_config_type:
622+
logger.warning('Must change both settings --multicontainer-config-file FILE --multicontainer-config-type TYPE')
623+
559624
return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name, name, settings))
560625

561626

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

587652

588-
def show_container_settings(cmd, resource_group_name, name, slot=None):
653+
def show_container_settings(cmd, resource_group_name, name, show_multicontainer_config=None, slot=None):
589654
settings = get_app_settings(cmd, resource_group_name, name, slot)
590-
return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name,
591-
name, settings, slot))
655+
return _mask_creds_related_appsettings(_filter_for_container_settings(cmd, resource_group_name, name, settings,
656+
show_multicontainer_config, slot))
592657

593658

594-
def _filter_for_container_settings(cmd, resource_group_name, name, settings, slot=None):
659+
def _filter_for_container_settings(cmd, resource_group_name, name, settings,
660+
show_multicontainer_config=None, slot=None):
595661
result = [x for x in settings if x['name'] in CONTAINER_APPSETTING_NAMES]
596662
fx_version = _get_linux_fx_version(cmd, resource_group_name, name, slot).strip()
597663
if fx_version:
598664
added_image_name = {'name': 'DOCKER_CUSTOM_IMAGE_NAME',
599665
'value': fx_version}
600666
result.append(added_image_name)
667+
if show_multicontainer_config:
668+
decoded_value = _get_linux_multicontainer_decoded_config(cmd, resource_group_name, name, slot)
669+
decoded_image_name = {'name': 'DOCKER_CUSTOM_IMAGE_NAME_DECODED',
670+
'value': decoded_value}
671+
result.append(decoded_image_name)
601672
return result
602673

603674

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

11171188

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

12901360

12911361
def _get_log(url, user_name, password, log_file=None):
1292-
import sys
12931362
import certifi
12941363
import urllib3
12951364
try:

0 commit comments

Comments
 (0)