From abb2925ead8d4e5941f4e5595a214b30f4769b80 Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Thu, 26 Apr 2018 12:19:45 -0700 Subject: [PATCH 1/6] Initial tunnel commit --- src/webapp/azext_webapp/__init__.py | 3 + src/webapp/azext_webapp/_help.py | 9 ++ src/webapp/azext_webapp/custom.py | 37 +++++- src/webapp/azext_webapp/tunnel.py | 167 ++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/webapp/azext_webapp/tunnel.py diff --git a/src/webapp/azext_webapp/__init__.py b/src/webapp/azext_webapp/__init__.py index 5acbe9846b3..a5777d75e55 100644 --- a/src/webapp/azext_webapp/__init__.py +++ b/src/webapp/azext_webapp/__init__.py @@ -23,6 +23,7 @@ def __init__(self, cli_ctx=None): def load_command_table(self, _): with self.command_group('webapp') as g: g.custom_command('up', 'create_deploy_webapp') + g.custom_command('tunnel create','create_tunnel') return self.command_table def load_arguments(self, _): @@ -31,6 +32,8 @@ def load_arguments(self, _): c.argument('dryrun', help="shows summary of the create and deploy operation instead of executing it", default=False, action='store_true') + with self.argument_contect('webapp tunnel create') as c: + c.argument('port',options_list=['--port', '-p'], help='Port for the remote connection', type=int) COMMAND_LOADER_CLS = WebappExtCommandLoader diff --git a/src/webapp/azext_webapp/_help.py b/src/webapp/azext_webapp/_help.py index 2aa436e7cd7..ea682c09e07 100644 --- a/src/webapp/azext_webapp/_help.py +++ b/src/webapp/azext_webapp/_help.py @@ -16,3 +16,12 @@ az webapp up -n MyUniqueAppName --dryrun \n az webapp up -n MyUniqueAppName -l locationName """ +helps['webapp tunnel'] = """ + type: group + short-summary: Create a remote connection using a tcp tunnel to your app +""" + +helps['webapp tunnel create'] = """ + type: command + short-summary: Create a remote connection using a tcp tunnel to your app +""" diff --git a/src/webapp/azext_webapp/custom.py b/src/webapp/azext_webapp/custom.py index 34be7f65cfd..cc3244a6cbf 100644 --- a/src/webapp/azext_webapp/custom.py +++ b/src/webapp/azext_webapp/custom.py @@ -14,7 +14,9 @@ update_app_settings, _get_site_credential, _get_scm_url, - get_sku_name) + get_sku_name, + list_publish_profiles, + get_site_configs) from .create_util import ( zip_contents_from_dir, @@ -178,3 +180,36 @@ def create_deploy_webapp(cmd, name, location=None, dryrun=False): create_json.update({'app_url': url}) logger.warning("All done.") return create_json + +def _check_for_ready_tunnel(cmd, resource_group_name, name, remote_debugging, tunnel_server, slot=None): + from .tunnel import TunnelServer + default_port = tunnel_server.is_port_set_to_default() + if default_port is not remote_debugging: + return True + return False + +def create_tunnel(cmd, resource_group_name, name, port, slot=None): + profiles = list_publish_profiles(cmd, resource_group_name, name, slot) + user_name = next(p['userName'] for p in profiles) + user_password = next(p['userPWD'] for p in profiles) + import time + import threading + from .tunnel import TunnelServer + tunnel_server = TunnelServer('', port, name, user_name, user_password) + + config = get_site_configs(cmd, resource_group_name, name, slot) + + if not _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): + print('Tunnel is not ready yet, please wait (may take up to 1 minute)') + + t = threading.Thread() + t.daemon = True + t.start() + + while True: + time.sleep(1) + print('.') + if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, slot): + break + print('Tunnel is ready! Creating on port {}'.format(port)) + tunnel_server.start_server() \ No newline at end of file diff --git a/src/webapp/azext_webapp/tunnel.py b/src/webapp/azext_webapp/tunnel.py new file mode 100644 index 00000000000..04cfd235fbf --- /dev/null +++ b/src/webapp/azext_webapp/tunnel.py @@ -0,0 +1,167 @@ +import sys +import ssl +import socket +import time +import traceback +import websocket + +from contextlib import closing +from threading import Thread +from websocket import create_connection, WebSocket + +from knack.util import CLIError +from knack.log import get_logger +logger = get_logger(__name__) # TODO: Replace print with logger below + +class TunnelWebSocket(WebSocket): + def recv_frame(self): + frame = super(TunnelWebSocket, self).recv_frame() + print('Received frame:{}'.format(frame)) + return frame + + def recv(self): + data = super(TunnelWebSocket, self).recv() + print('Received websocket data:{}'.format(data)) + return data + + def send_binary(self, data): + super(TunnelWebSocket, self).send_binary(data) + +class TunnelServer(object): + def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote_password): + self.local_addr = local_addr + self.local_port = local_port + if not self.is_port_open(): + raise CLIError('Defined port is currently unavailable') + self.remote_addr = remote_addr + self.remote_user_name = remote_user_name + self.remote_password = remote_password + print('Creating a socket') + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + print('Setting socket options') + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + print('Binding to socket on local address and port') + self.sock.bind((self.local_addr, self.local_port)) + print('Got to the end of init') + + def create_basic_auth(self): + from base64 import b64encode, b64decode + basic_auth_string = '{}:{}'.format(self.remote_user_name, self.remote_password).encode() + basic_auth_string = b64encode(basic_auth_string).decode('utf-8') + return basic_auth_string + + def is_port_open(self): + is_port_open = False + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + if sock.connect_ex(('', self.local_port)) == 0: + print('Port is open') + is_port_open = True + else: + print('Port is not open') + return is_port_open + + def is_port_set_to_default(self): + import sys + import certifi + import urllib3 + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + + http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) + headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(self.remote_user_name, self.remote_password)) + url = 'https://{}{}'.format(self.remote_addr,'.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx?GetStatus') + r = http.request( + 'GET', + url, + headers=headers, + preload_content=False + ) + if r.status != 200: + raise CLIError("Failed to connect to '{}' with status code '{}' and reason '{}'".format(url, r.status, r.reason)) + if '2222' in r.text: + return True + return False + + + def listen(self): + self.sock.listen(100) + index = 0 + basic_auth_string = self.create_basic_auth() + while True: + self.client, address = self.sock.accept() + self.client.settimeout(60) + host = 'wss://{}{}'.format(self.remote_addr,'.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx') + basic_auth_header = 'Authorization: Basic {}'.format(basic_auth_string) + websocket.enableTrace(True) + self.ws = create_connection(host, + sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), + class_ = TunnelWebSocket, + header = [basic_auth_header], + sslopt={'cert_reqs': ssl.CERT_NONE}, + enable_multithread=True) + print('websocket, connected status:{}', self.ws.connected) + + index = index + 1 + print('got debugger connection... index:{}'.format(index)) + debugger_thread = Thread(target = self.listen_to_client, args = (self.client, self.ws, index)) + web_socket_thread = Thread(target = self.listen_to_web_socket, args = (self.client, self.ws, index)) + debugger_thread.start() + web_socket_thread.start() + print('both threads started...') + debugger_thread.join() + web_socket_thread.join() + print('both threads stopped...') + + def listen_to_web_socket(self, client, ws_socket, index): + size = 4096 + while True: + try: + print('Waiting for websocket data, connection status:{}, index:{}'.format(ws_socket.connected, index)) + data = ws_socket.recv() + print('Received websocket data:{}, index:{}'.format(data, index)) + if data: + # Set the response to echo back the recieved data + response = data + print('Sending to debugger, response:{}, index{}'.format(response, index)) + client.sendall(response) + print('Done sending to debugger, index:{}', index) + else: + print('Client disconnected!, index:{}', index) + client.close() + ws_socket.close() + break + except: + traceback.print_exc(file=sys.stdout) + client.close() + ws_socket.close() + return False + + def listen_to_client(self, client, ws_socket, index): + size = 4096 + while True: + try: + print('Waiting for debugger data, index:{}'.format(index)) + buf = bytearray(4096) + nbytes = client.recv_into(buf, 4096) + print('Received debugger data, nbytes:{}, index:{}'.format(nbytes, index)) + if nbytes > 0: + responseData = buf[0:nbytes] + print('Sending to websocket, response data:{}, index:{}'.format(responseData, index)) + ws_socket.send_binary(responseData) + print('Done sending to websocket, index:{}', index) + else: + logger.warn('Client disconnected %s', index) + client.close() + ws_socket.close() + break + except: + traceback.print_exc(file=sys.stdout) + client.close() + ws_socket.close() + return False + + def start_server(self): + self.listen() From 974b01bfdeae4dea98fa6a615c9338bd8a8a7b2c Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Wed, 2 May 2018 09:00:15 -0700 Subject: [PATCH 2/6] Fix logging statements and command name --- src/webapp/azext_webapp/__init__.py | 6 +-- src/webapp/azext_webapp/_help.py | 5 +- src/webapp/azext_webapp/custom.py | 67 ++++++++++++----------- src/webapp/azext_webapp/tunnel.py | 84 +++++++++++++++-------------- src/webapp/setup.py | 2 +- 5 files changed, 86 insertions(+), 78 deletions(-) diff --git a/src/webapp/azext_webapp/__init__.py b/src/webapp/azext_webapp/__init__.py index f2f3b0d04bf..bc1ad1f9ae4 100644 --- a/src/webapp/azext_webapp/__init__.py +++ b/src/webapp/azext_webapp/__init__.py @@ -23,7 +23,7 @@ def __init__(self, cli_ctx=None): def load_command_table(self, _): with self.command_group('webapp') as g: g.custom_command('up', 'create_deploy_webapp') - g.custom_command('tunnel create','create_tunnel') + g.custom_command('remote-connection create', 'create_tunnel') g.custom_command('config snapshot list', 'list_webapp_snapshots') g.custom_command('config snapshot restore', 'restore_webapp_snapshot') return self.command_table @@ -34,8 +34,8 @@ def load_arguments(self, _): c.argument('dryrun', help="shows summary of the create and deploy operation instead of executing it", default=False, action='store_true') - with self.argument_contect('webapp tunnel create') as c: - c.argument('port',options_list=['--port', '-p'], help='Port for the remote connection', type=int) + with self.argument_context('webapp remote-connection create') as c: + c.argument('port', options_list=['--port', '-p'], help='Port for the remote connection', type=int) with self.argument_context('webapp config snapshot list') as c: c.argument('resource_group', options_list=['--resource-group', '-g'], help='Name of resource group.') c.argument('name', options_list=['--webapp-name', '-n'], help='Name of the webapp.') diff --git a/src/webapp/azext_webapp/_help.py b/src/webapp/azext_webapp/_help.py index c8d9cb2f065..eca37b0e1d5 100644 --- a/src/webapp/azext_webapp/_help.py +++ b/src/webapp/azext_webapp/_help.py @@ -16,14 +16,15 @@ az webapp up -n MyUniqueAppName --dryrun \n az webapp up -n MyUniqueAppName -l locationName """ -helps['webapp tunnel'] = """ +helps['webapp remote-connection'] = """ type: group short-summary: Create a remote connection using a tcp tunnel to your app """ -helps['webapp tunnel create'] = """ +helps['webapp remote-connection create'] = """ type: command short-summary: Create a remote connection using a tcp tunnel to your app +""" helps['webapp config snapshot list'] = """ type: command diff --git a/src/webapp/azext_webapp/custom.py b/src/webapp/azext_webapp/custom.py index 5e34b234bd2..b940f323736 100644 --- a/src/webapp/azext_webapp/custom.py +++ b/src/webapp/azext_webapp/custom.py @@ -184,38 +184,6 @@ def create_deploy_webapp(cmd, name, location=None, dryrun=False): logger.warning("All done.") return create_json -def _check_for_ready_tunnel(cmd, resource_group_name, name, remote_debugging, tunnel_server, slot=None): - from .tunnel import TunnelServer - default_port = tunnel_server.is_port_set_to_default() - if default_port is not remote_debugging: - return True - return False - -def create_tunnel(cmd, resource_group_name, name, port, slot=None): - profiles = list_publish_profiles(cmd, resource_group_name, name, slot) - user_name = next(p['userName'] for p in profiles) - user_password = next(p['userPWD'] for p in profiles) - import time - import threading - from .tunnel import TunnelServer - tunnel_server = TunnelServer('', port, name, user_name, user_password) - - config = get_site_configs(cmd, resource_group_name, name, slot) - - if not _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): - print('Tunnel is not ready yet, please wait (may take up to 1 minute)') - - t = threading.Thread() - t.daemon = True - t.start() - - while True: - time.sleep(1) - print('.') - if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, slot): - break - print('Tunnel is ready! Creating on port {}'.format(port)) - tunnel_server.start_server() def list_webapp_snapshots(cmd, resource_group, name, slot=None): client = web_client_factory(cmd.cli_ctx) @@ -247,3 +215,38 @@ def restore_webapp_snapshot(cmd, resource_group, name, time, slot=None, restore_ return client.web_apps.recover_slot(resource_group, name, request, slot) else: return client.web_apps.recover(resource_group, name, request) + + +def _check_for_ready_tunnel(cmd, resource_group_name, name, remote_debugging, tunnel_server, slot=None): + from .tunnel import TunnelServer + default_port = tunnel_server.is_port_set_to_default() + if default_port is not remote_debugging: + return True + return False + + +def create_tunnel(cmd, resource_group_name, name, port, slot=None): + profiles = list_publish_profiles(cmd, resource_group_name, name, slot) + user_name = next(p['userName'] for p in profiles) + user_password = next(p['userPWD'] for p in profiles) + import time + import threading + from .tunnel import TunnelServer + tunnel_server = TunnelServer('', port, name, user_name, user_password) + + config = get_site_configs(cmd, resource_group_name, name, slot) + + if not _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): + print('Tunnel is not ready yet, please wait (may take up to 1 minute)') + + t = threading.Thread() + t.daemon = True + t.start() + + while True: + time.sleep(1) + print('.') + if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, slot): + break + print('Tunnel is ready! Creating on port {}'.format(port)) + tunnel_server.start_server() diff --git a/src/webapp/azext_webapp/tunnel.py b/src/webapp/azext_webapp/tunnel.py index 04cfd235fbf..37ece96d20e 100644 --- a/src/webapp/azext_webapp/tunnel.py +++ b/src/webapp/azext_webapp/tunnel.py @@ -1,3 +1,8 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + import sys import ssl import socket @@ -11,22 +16,24 @@ from knack.util import CLIError from knack.log import get_logger -logger = get_logger(__name__) # TODO: Replace print with logger below +logger = get_logger(__name__) + class TunnelWebSocket(WebSocket): def recv_frame(self): frame = super(TunnelWebSocket, self).recv_frame() - print('Received frame:{}'.format(frame)) + logger.info('Received frame: %s', frame) return frame - + def recv(self): data = super(TunnelWebSocket, self).recv() - print('Received websocket data:{}'.format(data)) + logger.info('Received websocket data: %s', data) return data def send_binary(self, data): super(TunnelWebSocket, self).send_binary(data) + class TunnelServer(object): def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote_password): self.local_addr = local_addr @@ -36,13 +43,13 @@ def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote self.remote_addr = remote_addr self.remote_user_name = remote_user_name self.remote_password = remote_password - print('Creating a socket') + logger.info('Creating a socket on port: %s', self.local_port) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - print('Setting socket options') + logger.info('Setting socket options') self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - print('Binding to socket on local address and port') + logger.info('Binding to socket on local address and port') self.sock.bind((self.local_addr, self.local_port)) - print('Got to the end of init') + logger.info('Finished initialization') def create_basic_auth(self): from base64 import b64encode, b64decode @@ -54,12 +61,12 @@ def is_port_open(self): is_port_open = False with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: if sock.connect_ex(('', self.local_port)) == 0: - print('Port is open') + logger.info('Port %s is open', self.local_port) is_port_open = True else: - print('Port is not open') + logger.warning('Port %s is NOT open', self.local_port) return is_port_open - + def is_port_set_to_default(self): import sys import certifi @@ -69,10 +76,10 @@ def is_port_set_to_default(self): urllib3.contrib.pyopenssl.inject_into_urllib3() except ImportError: pass - + http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where()) headers = urllib3.util.make_headers(basic_auth='{0}:{1}'.format(self.remote_user_name, self.remote_password)) - url = 'https://{}{}'.format(self.remote_addr,'.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx?GetStatus') + url = 'https://{}{}'.format(self.remote_addr, '.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx?GetStatus') r = http.request( 'GET', url, @@ -84,8 +91,7 @@ def is_port_set_to_default(self): if '2222' in r.text: return True return False - - + def listen(self): self.sock.listen(100) index = 0 @@ -93,45 +99,44 @@ def listen(self): while True: self.client, address = self.sock.accept() self.client.settimeout(60) - host = 'wss://{}{}'.format(self.remote_addr,'.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx') + host = 'wss://{}{}'.format(self.remote_addr, '.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx') basic_auth_header = 'Authorization: Basic {}'.format(basic_auth_string) websocket.enableTrace(True) self.ws = create_connection(host, - sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), - class_ = TunnelWebSocket, - header = [basic_auth_header], - sslopt={'cert_reqs': ssl.CERT_NONE}, + sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), + class_=TunnelWebSocket, + header=[basic_auth_header], + sslopt={'cert_reqs': ssl.CERT_NONE}, enable_multithread=True) - print('websocket, connected status:{}', self.ws.connected) + logger.info('Websocket, connected status: %s', self.ws.connected) index = index + 1 - print('got debugger connection... index:{}'.format(index)) - debugger_thread = Thread(target = self.listen_to_client, args = (self.client, self.ws, index)) - web_socket_thread = Thread(target = self.listen_to_web_socket, args = (self.client, self.ws, index)) + logger.info('Got debugger connection... index: %s', index) + debugger_thread = Thread(target=self.listen_to_client, args=(self.client, self.ws, index)) + web_socket_thread = Thread(target=self.listen_to_web_socket, args=(self.client, self.ws, index)) debugger_thread.start() web_socket_thread.start() - print('both threads started...') + logger.info('Both debugger and websocket threads started...') debugger_thread.join() web_socket_thread.join() - print('both threads stopped...') + logger.info('Both debugger and websocket threads stopped...') def listen_to_web_socket(self, client, ws_socket, index): - size = 4096 while True: try: - print('Waiting for websocket data, connection status:{}, index:{}'.format(ws_socket.connected, index)) + logger.info('Waiting for websocket data, connection status: %s, index: %s', ws_socket.connected, index) data = ws_socket.recv() - print('Received websocket data:{}, index:{}'.format(data, index)) + logger.info('Received websocket data: %s, index: %s', data, index) if data: - # Set the response to echo back the recieved data + # Set the response to echo back the recieved data response = data - print('Sending to debugger, response:{}, index{}'.format(response, index)) + logger.info('Sending to debugger, response: %s, index: %s', response, index) client.sendall(response) - print('Done sending to debugger, index:{}', index) + logger.info('Done sending to debugger, index: %s', index) else: - print('Client disconnected!, index:{}', index) + logger.info('Client disconnected!, index: %s', index) client.close() - ws_socket.close() + ws_socket.close() break except: traceback.print_exc(file=sys.stdout) @@ -140,18 +145,17 @@ def listen_to_web_socket(self, client, ws_socket, index): return False def listen_to_client(self, client, ws_socket, index): - size = 4096 while True: try: - print('Waiting for debugger data, index:{}'.format(index)) + logger.info('Waiting for debugger data, index: %s', index) buf = bytearray(4096) nbytes = client.recv_into(buf, 4096) - print('Received debugger data, nbytes:{}, index:{}'.format(nbytes, index)) + logger.info('Received debugger data, nbytes: %s, index: %s', nbytes, index) if nbytes > 0: responseData = buf[0:nbytes] - print('Sending to websocket, response data:{}, index:{}'.format(responseData, index)) + logger.info('Sending to websocket, response data: %s, index: %s', responseData, index) ws_socket.send_binary(responseData) - print('Done sending to websocket, index:{}', index) + logger.info('Done sending to websocket, index: %s', index) else: logger.warn('Client disconnected %s', index) client.close() @@ -164,4 +168,4 @@ def listen_to_client(self, client, ws_socket, index): return False def start_server(self): - self.listen() + self.listen() diff --git a/src/webapp/setup.py b/src/webapp/setup.py index a5c94a56fb2..d9651b249a7 100644 --- a/src/webapp/setup.py +++ b/src/webapp/setup.py @@ -32,7 +32,7 @@ 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='Sisira Panchagnula, Lukasz Stempniewicz', author_email='sisirap@microsoft.com', url='https://github.com/Azure/azure-cli-extensions', classifiers=CLASSIFIERS, From 0b5ddccbf7366d5c42bbce72005659fb2ab99720 Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Wed, 2 May 2018 10:32:02 -0700 Subject: [PATCH 3/6] Minor bug fixes --- src/webapp/azext_webapp/__init__.py | 1 + src/webapp/azext_webapp/tunnel.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/webapp/azext_webapp/__init__.py b/src/webapp/azext_webapp/__init__.py index bc1ad1f9ae4..ca5586e6aa2 100644 --- a/src/webapp/azext_webapp/__init__.py +++ b/src/webapp/azext_webapp/__init__.py @@ -36,6 +36,7 @@ def load_arguments(self, _): default=False, action='store_true') with self.argument_context('webapp remote-connection create') as c: c.argument('port', options_list=['--port', '-p'], help='Port for the remote connection', type=int) + c.argument('name', options_list=['--name', '-n'], help='Name of the webapp to connect to') with self.argument_context('webapp config snapshot list') as c: c.argument('resource_group', options_list=['--resource-group', '-g'], help='Name of resource group.') c.argument('name', options_list=['--webapp-name', '-n'], help='Name of the webapp.') diff --git a/src/webapp/azext_webapp/tunnel.py b/src/webapp/azext_webapp/tunnel.py index 37ece96d20e..2ba6e8946fb 100644 --- a/src/webapp/azext_webapp/tunnel.py +++ b/src/webapp/azext_webapp/tunnel.py @@ -61,10 +61,10 @@ def is_port_open(self): is_port_open = False with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: if sock.connect_ex(('', self.local_port)) == 0: - logger.info('Port %s is open', self.local_port) - is_port_open = True + logger.info('Port %s is NOT open', self.local_port) else: - logger.warning('Port %s is NOT open', self.local_port) + logger.warning('Port %s is open', self.local_port) + is_port_open = True return is_port_open def is_port_set_to_default(self): @@ -88,7 +88,9 @@ def is_port_set_to_default(self): ) if r.status != 200: raise CLIError("Failed to connect to '{}' with status code '{}' and reason '{}'".format(url, r.status, r.reason)) - if '2222' in r.text: + msg = r.read().decode('utf-8') + logger.info('Status response message: %s', msg) + if '2222' in msg: return True return False @@ -117,9 +119,11 @@ def listen(self): debugger_thread.start() web_socket_thread.start() logger.info('Both debugger and websocket threads started...') + print('Successfully started local server..') debugger_thread.join() web_socket_thread.join() logger.info('Both debugger and websocket threads stopped...') + print('Stopped local server..') def listen_to_web_socket(self, client, ws_socket, index): while True: @@ -168,4 +172,5 @@ def listen_to_client(self, client, ws_socket, index): return False def start_server(self): + print('Starting local server..') self.listen() From d6527dfdfa26208d503bae3585a1146cf0ae4454 Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Wed, 2 May 2018 12:40:02 -0700 Subject: [PATCH 4/6] Update whl file version --- src/index.json | 8 ++++---- src/webapp/azext_webapp/custom.py | 9 +++------ src/webapp/setup.py | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/index.json b/src/index.json index b512142120f..fb2f12a0758 100644 --- a/src/index.json +++ b/src/index.json @@ -325,9 +325,9 @@ ], "webapp": [ { - "filename": "webapp-0.2.2-py2.py3-none-any.whl", - "sha256Digest": "db8bdba11e6814ceeff37063d0e7548dab42f9e33a64b4c4a5b7ffea0bc93884", - "downloadUrl": "https://github.com/panchagnula/azure-cli-extensions/raw/sisirap-extensions-whl/dist/webapp-0.2.2-py2.py3-none-any.whl", + "filename": "webapp-0.2.3-py2.py3-none-any.whl", + "sha256Digest": "ba4f5d2004a5f06deeab89af435b0bf419f032ffa5b80833a32520ccc0fb6b4e", + "downloadUrl": "https://github.com/panchagnula/azure-cli-extensions/raw/sisirap-extensions-whl/dist/webapp-0.2.3-py2.py3-none-any.whl", "metadata": { "azext.isPreview": true, "azext.minCliCoreVersion": "2.0.24", @@ -366,7 +366,7 @@ "metadata_version": "2.0", "name": "webapp", "summary": "An Azure CLI Extension to manage appservice resources", - "version": "0.2.2" + "version": "0.2.3" } } ], diff --git a/src/webapp/azext_webapp/custom.py b/src/webapp/azext_webapp/custom.py index b940f323736..027785857a8 100644 --- a/src/webapp/azext_webapp/custom.py +++ b/src/webapp/azext_webapp/custom.py @@ -233,16 +233,13 @@ def create_tunnel(cmd, resource_group_name, name, port, slot=None): import threading from .tunnel import TunnelServer tunnel_server = TunnelServer('', port, name, user_name, user_password) - config = get_site_configs(cmd, resource_group_name, name, slot) + t = threading.Thread() + t.daemon = True + t.start() if not _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): print('Tunnel is not ready yet, please wait (may take up to 1 minute)') - - t = threading.Thread() - t.daemon = True - t.start() - while True: time.sleep(1) print('.') diff --git a/src/webapp/setup.py b/src/webapp/setup.py index d9651b249a7..78727e9507d 100644 --- a/src/webapp/setup.py +++ b/src/webapp/setup.py @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "0.2.2" +VERSION = "0.2.3" CLASSIFIERS = [ 'Development Status :: 4 - Beta', From 838627595e4edc2f037cf084b68f23c368b6de20 Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Wed, 2 May 2018 14:34:05 -0700 Subject: [PATCH 5/6] Address comments --- src/index.json | 4 ++-- src/webapp/azext_webapp/_help.py | 4 ++-- src/webapp/azext_webapp/custom.py | 6 +++--- src/webapp/azext_webapp/tunnel.py | 18 ++++++++++++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/index.json b/src/index.json index fb2f12a0758..375f5fbe522 100644 --- a/src/index.json +++ b/src/index.json @@ -234,7 +234,7 @@ }, { "filename": "image_copy_extension-0.0.6-py2.py3-none-any.whl", - "sha256Digest": "986ab7ab186974bb2c365bf4092ed5dd554b00017ddf4c70ea07a53bcaa6bcc7", + "sha256Digest": "ad128ee3fb0bb992dc8487d0d5538c231891b2fd98fa0ea20d71c873db016b46", "downloadUrl": "https://files.pythonhosted.org/packages/ed/60/306879ce292e087d329ed15c7c63f42e880371ec8cc624c17bb28a1f937b/image_copy_extension-0.0.6-py2.py3-none-any.whl", "metadata": { "azext.minCliCoreVersion": "2.0.24", @@ -349,7 +349,7 @@ "contacts": [ { "email": "sisirap@microsoft.com", - "name": "Sisira Panchagnula", + "name": "Sisira Panchagnula, Lukasz Stempniewicz", "role": "author" } ], diff --git a/src/webapp/azext_webapp/_help.py b/src/webapp/azext_webapp/_help.py index eca37b0e1d5..dbb591168b0 100644 --- a/src/webapp/azext_webapp/_help.py +++ b/src/webapp/azext_webapp/_help.py @@ -18,12 +18,12 @@ """ helps['webapp remote-connection'] = """ type: group - short-summary: Create a remote connection using a tcp tunnel to your app + short-summary: Create a remote connection using a tcp tunnel to your web app """ helps['webapp remote-connection create'] = """ type: command - short-summary: Create a remote connection using a tcp tunnel to your app + short-summary: Creates a remote connection using a tcp tunnel to your web app """ helps['webapp config snapshot list'] = """ diff --git a/src/webapp/azext_webapp/custom.py b/src/webapp/azext_webapp/custom.py index 027785857a8..aab915c3089 100644 --- a/src/webapp/azext_webapp/custom.py +++ b/src/webapp/azext_webapp/custom.py @@ -239,11 +239,11 @@ def create_tunnel(cmd, resource_group_name, name, port, slot=None): t.daemon = True t.start() if not _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): - print('Tunnel is not ready yet, please wait (may take up to 1 minute)') + logger.warning('Tunnel is not ready yet, please wait (may take up to 1 minute)') while True: time.sleep(1) - print('.') + logger.warning('.') if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, slot): break - print('Tunnel is ready! Creating on port {}'.format(port)) + logger.warning('Tunnel is ready! Creating on port %s', port) tunnel_server.start_server() diff --git a/src/webapp/azext_webapp/tunnel.py b/src/webapp/azext_webapp/tunnel.py index 2ba6e8946fb..966d6e37839 100644 --- a/src/webapp/azext_webapp/tunnel.py +++ b/src/webapp/azext_webapp/tunnel.py @@ -9,6 +9,7 @@ import time import traceback import websocket +import logging as logs from contextlib import closing from threading import Thread @@ -90,6 +91,8 @@ def is_port_set_to_default(self): raise CLIError("Failed to connect to '{}' with status code '{}' and reason '{}'".format(url, r.status, r.reason)) msg = r.read().decode('utf-8') logger.info('Status response message: %s', msg) + if 'FAIL' in msg.upper(): + logger.warning('WARNING - Remote debugging may not be setup properly. Reponse content: %s', msg) if '2222' in msg: return True return False @@ -103,7 +106,14 @@ def listen(self): self.client.settimeout(60) host = 'wss://{}{}'.format(self.remote_addr, '.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx') basic_auth_header = 'Authorization: Basic {}'.format(basic_auth_string) - websocket.enableTrace(True) + cli_logger = get_logger() # get CLI logger which has the level set through command lines + is_verbose = any(handler.level <= logs.INFO for handler in cli_logger.handlers) + if is_verbose: + logger.info('Websocket tracing enabled') + websocket.enableTrace(True) + else: + logger.warning('Websocket tracing disabled, use --verbose flag to enable') + websocket.enableTrace(False) self.ws = create_connection(host, sockopt=((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),), class_=TunnelWebSocket, @@ -119,11 +129,11 @@ def listen(self): debugger_thread.start() web_socket_thread.start() logger.info('Both debugger and websocket threads started...') - print('Successfully started local server..') + logger.warning('Successfully started local server..') debugger_thread.join() web_socket_thread.join() logger.info('Both debugger and websocket threads stopped...') - print('Stopped local server..') + logger.warning('Stopped local server..') def listen_to_web_socket(self, client, ws_socket, index): while True: @@ -172,5 +182,5 @@ def listen_to_client(self, client, ws_socket, index): return False def start_server(self): - print('Starting local server..') + logger.warning('Starting local server..') self.listen() From 199661e106cf6d2c0654bb0c78007218979c9966 Mon Sep 17 00:00:00 2001 From: Lukasz Stempniewicz Date: Wed, 2 May 2018 14:49:25 -0700 Subject: [PATCH 6/6] Update hash for wheel file --- src/index.json | 4 ++-- src/webapp/azext_webapp/custom.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.json b/src/index.json index 375f5fbe522..a1e3f6b3b85 100644 --- a/src/index.json +++ b/src/index.json @@ -234,7 +234,7 @@ }, { "filename": "image_copy_extension-0.0.6-py2.py3-none-any.whl", - "sha256Digest": "ad128ee3fb0bb992dc8487d0d5538c231891b2fd98fa0ea20d71c873db016b46", + "sha256Digest": "986ab7ab186974bb2c365bf4092ed5dd554b00017ddf4c70ea07a53bcaa6bcc7", "downloadUrl": "https://files.pythonhosted.org/packages/ed/60/306879ce292e087d329ed15c7c63f42e880371ec8cc624c17bb28a1f937b/image_copy_extension-0.0.6-py2.py3-none-any.whl", "metadata": { "azext.minCliCoreVersion": "2.0.24", @@ -326,7 +326,7 @@ "webapp": [ { "filename": "webapp-0.2.3-py2.py3-none-any.whl", - "sha256Digest": "ba4f5d2004a5f06deeab89af435b0bf419f032ffa5b80833a32520ccc0fb6b4e", + "sha256Digest": "c6e2c8fff7f3d88f9b7eb77327d67ab525ad9c8b8b27b3b004b565fac391c241", "downloadUrl": "https://github.com/panchagnula/azure-cli-extensions/raw/sisirap-extensions-whl/dist/webapp-0.2.3-py2.py3-none-any.whl", "metadata": { "azext.isPreview": true, diff --git a/src/webapp/azext_webapp/custom.py b/src/webapp/azext_webapp/custom.py index aab915c3089..1f3397c8f9e 100644 --- a/src/webapp/azext_webapp/custom.py +++ b/src/webapp/azext_webapp/custom.py @@ -243,7 +243,7 @@ def create_tunnel(cmd, resource_group_name, name, port, slot=None): while True: time.sleep(1) logger.warning('.') - if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, slot): + if _check_for_ready_tunnel(cmd, resource_group_name, name, config.remote_debugging_enabled, tunnel_server, slot): break logger.warning('Tunnel is ready! Creating on port %s', port) tunnel_server.start_server()