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 @@ -3,6 +3,7 @@
Release History
===============
* functionapp: add support for app insights on functionapp create
* webapp: bugfixes for webapp ssh

0.2.11
++++++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# --------------------------------------------------------------------------------------------

from __future__ import print_function
import subprocess
import threading
import time

Expand All @@ -21,6 +20,9 @@
import sys
import OpenSSL.crypto

from fabric import Connection


from knack.prompting import prompt_pass, NoTTYException
from knack.util import CLIError
from knack.log import get_logger
Expand Down Expand Up @@ -2253,11 +2255,11 @@ def _ping_scm_site(cmd, resource_group, name):
requests.get(scm_url + '/api/settings', headers=authorization)


def _check_for_ready_tunnel(tunnel_server):
return tunnel_server.is_port_set_to_default()
def is_webapp_up(tunnel_server):
return tunnel_server.is_webapp_up()


def create_tunnel(cmd, resource_group_name, name, port=None, slot=None):
def create_tunnel_and_session(cmd, resource_group_name, name, port=None, slot=None):
webapp = show_webapp(cmd, resource_group_name, name, slot)
is_linux = webapp.reserved
if not is_linux:
Expand All @@ -2281,46 +2283,70 @@ def create_tunnel(cmd, resource_group_name, name, port=None, slot=None):
tunnel_server = TunnelServer('', port, host_name, profile_user_name, profile_user_password)
_ping_scm_site(cmd, resource_group_name, name)

_wait_for_webapp(tunnel_server)

t = threading.Thread(target=_start_tunnel, args=(tunnel_server,))
t.daemon = True
t.start()

_wait_for_tunnel(tunnel_server, False)
logger.warning("SSH is available ( username: %s, password: %s )", ssh_user_name, ssh_user_password)

s = threading.Thread(target=_start_ssh,
args=('localhost', tunnel_server.get_port(), ssh_user_name))
s = threading.Thread(target=_start_ssh_session,
args=('localhost', tunnel_server.get_port(), ssh_user_name, ssh_user_password))
s.daemon = True
s.start()

while s.isAlive() and t.isAlive():
time.sleep(5)


def _wait_for_tunnel(tunnel_server, print_warnings):
if not _check_for_ready_tunnel(tunnel_server):
if print_warnings:
logger.warning('Tunnel is not ready yet, please wait (may take up to 1 minute)')
while True:
time.sleep(1)
if print_warnings:
logger.warning('.')
if _check_for_ready_tunnel(tunnel_server):
break
def _wait_for_webapp(tunnel_server):
tries = 0
while True:
if is_webapp_up(tunnel_server):
break
if tries == 0:
logger.warning('Connection is not ready yet, please wait')
if tries == 60:
raise CLIError("Timeout Error, Unable to establish a connection")
tries = tries + 1
logger.warning('.')
time.sleep(1)


def _start_tunnel(tunnel_server):
_wait_for_tunnel(tunnel_server, True)
tunnel_server.start_server()


def _start_ssh(host_name, port, user_name):
subprocess.call("ssh -o StrictHostKeyChecking=no {}@{} -p {}".format(user_name, host_name, port), shell=True)
def _start_ssh_session(hostname, port, username, password):
tries = 0
while True:
try:
c = Connection(host=hostname,
port=port,
user=username,
# connect_timeout=60*10,
connect_kwargs={"password": password})
break
except Exception as ex: # pylint: disable=broad-except
logger.info(ex)
if tries == 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest you log an verbose entry to document the exception details: logger.info(ex). My concern is catching generic exceptions above could swallow non connection related errors which ends up making the diagnosis pretty hard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

logger.warning('Connection is not ready yet, please wait')
if tries == 60:
raise CLIError("Timeout Error, Unable to establish a connection")
tries = tries + 1
logger.warning('.')
time.sleep(1)
try:
c.run('cat /etc/motd', pty=True)
c.run('source /etc/profile; /bin/ash', pty=True)

Choose a reason for hiding this comment

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

this is supposed to be /bin/bash right?

except Exception as ex: # pylint: disable=broad-except
logger.info(ex)
finally:
c.close()


def ssh_webapp(cmd, resource_group_name, name, slot=None): # pylint: disable=too-many-statements
import platform
if platform.system() == "Windows":
raise CLIError('webapp ssh is only supported on linux and mac')
else:
create_tunnel(cmd, resource_group_name, name, port=None, slot=slot)
create_tunnel_and_session(cmd, resource_group_name, name, port=None, slot=slot)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2018 Jeff Forcier.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(self, local_addr, local_port, remote_addr, remote_user_name, remote
self.sock.bind((self.local_addr, self.local_port))
if self.local_port == 0:
self.local_port = self.sock.getsockname()[1]
logger.warning('Auto-selecting port: %s', self.local_port)
logger.info('Auto-selecting port: %s', self.local_port)
logger.info('Finished initialization')

def create_basic_auth(self):
Expand All @@ -69,11 +69,11 @@ def is_port_open(self):
if sock.connect_ex(('', self.local_port)) == 0:
logger.info('Port %s is NOT open', self.local_port)
else:
logger.warning('Port %s is open', self.local_port)
logger.info('Port %s is open', self.local_port)
is_port_open = True
return is_port_open

def is_port_set_to_default(self):
def is_webapp_up(self):
import certifi
import urllib3
try:
Expand All @@ -97,7 +97,8 @@ def is_port_set_to_default(self):
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)
logger.info('WARNING - Remote debugging may not be setup properly. Reponse content: %s', msg)
return False
if '2222' in msg:
return True
return False
Expand All @@ -108,7 +109,7 @@ def _listen(self):
basic_auth_string = self.create_basic_auth()
while True:
self.client, _address = self.sock.accept()
self.client.settimeout(1800)
self.client.settimeout(60 * 20)
host = 'wss://{}{}'.format(self.remote_addr, '.scm.azurewebsites.net/AppServiceTunnel/Tunnel.ashx')
basic_auth_header = 'Authorization: Basic {}'.format(basic_auth_string)
cli_logger = get_logger() # get CLI logger which has the level set through command lines
Expand All @@ -117,13 +118,14 @@ def _listen(self):
logger.info('Websocket tracing enabled')
websocket.enableTrace(True)
else:
logger.warning('Websocket tracing disabled, use --verbose flag to enable')
logger.info('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,
header=[basic_auth_header],
sslopt={'cert_reqs': ssl.CERT_NONE},
timeout=60 * 20,
enable_multithread=True)
logger.info('Websocket, connected status: %s', self.ws.connected)
index = index + 1
Expand All @@ -133,15 +135,15 @@ def _listen(self):
debugger_thread.start()
web_socket_thread.start()
logger.info('Both debugger and websocket threads started...')
logger.warning('Successfully connected to local server..')
logger.info('Successfully connected to local server..')
debugger_thread.join()
web_socket_thread.join()
logger.info('Both debugger and websocket threads stopped...')
logger.warning('Stopped local server..')
logger.info('Stopped local server..')

def _listen_to_web_socket(self, client, ws_socket, index):
while True:
try:
try:
while True:
logger.info('Waiting for websocket data, connection status: %s, index: %s', ws_socket.connected, index)
data = ws_socket.recv()
logger.info('Received websocket data: %s, index: %s', data, index)
Expand All @@ -152,17 +154,17 @@ def _listen_to_web_socket(self, client, ws_socket, index):
client.sendall(response)
logger.info('Done sending to debugger, index: %s', index)
else:
logger.info('Client disconnected!, index: %s', index)
client.close()
ws_socket.close()
break
except:
client.close()
ws_socket.close()
except Exception as ex: # pylint: disable=broad-except
logger.info(ex)
finally:
logger.info('Client disconnected!, index: %s', index)
client.close()
ws_socket.close()

def _listen_to_client(self, client, ws_socket, index):
while True:
try:
try:
while True:
logger.info('Waiting for debugger data, index: %s', index)
buf = bytearray(4096)
nbytes = client.recv_into(buf, 4096)
Expand All @@ -173,14 +175,14 @@ def _listen_to_client(self, client, ws_socket, index):
ws_socket.send_binary(responseData)
logger.info('Done sending to websocket, index: %s', index)
else:
logger.warning('Client disconnected %s', index)
client.close()
ws_socket.close()
break
except:
traceback.print_exc(file=sys.stdout)
client.close()
ws_socket.close()
except Exception as ex: # pylint: disable=broad-except
logger.info(ex)
logger.warning("Connection Timed Out")
finally:
logger.info('Client disconnected %s', index)
client.close()
ws_socket.close()

def start_server(self):
self._listen()
Expand Down
2 changes: 2 additions & 0 deletions src/command_modules/azure-cli-appservice/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
# v1.17 breaks on wildcard cert https://github.com/shazow/urllib3/issues/981
'urllib3[secure]>=1.18',
'xmltodict',
'fabric>=2.4',
'cryptography<2.5',
'pyOpenSSL',
'six',
'vsts-cd-manager<1.1.0',
Expand Down