@ -19,20 +19,21 @@
################################################
################################################
import warnings
import warnings
# prevent paramiko warning noise
# see http://stackoverflow.com/questions/3920502/
with warnings . catch_warnings ( ) :
warnings . simplefilter ( " ignore " )
import paramiko
import traceback
import traceback
import os
import os
import time
import time
import random
import re
import re
import shutil
import shutil
import subprocess
import subprocess
import pipes
from ansible import errors
from ansible import errors
# prevent paramiko warning noise
# see http://stackoverflow.com/questions/3920502/
with warnings . catch_warnings ( ) :
warnings . simplefilter ( " ignore " )
import paramiko
################################################
################################################
@ -94,65 +95,44 @@ class ParamikoConnection(object):
return ssh
return ssh
def connect ( self ) :
def connect ( self ) :
''' connect to the remote host '''
''' connect to the remote host '''
self . ssh = self . _get_conn ( )
self . ssh = self . _get_conn ( )
return self
return self
def exec_command ( self , cmd , tmp_path , sudoable = False ) :
def exec_command ( self , cmd , tmp_path , sudoable = False ) : # pylint: disable-msg=W0613
''' run a command on the remote host '''
''' run a command on the remote host '''
if not self . runner . sudo or not sudoable :
if not self . runner . sudo or not sudoable :
stdin , stdout , stderr = self . ssh . exec_command ( cmd )
stdin , stdout , stderr = self . ssh . exec_command ( cmd )
return ( stdin , stdout , stderr )
return ( stdin , stdout , stderr )
else :
else :
# percalculated tmp_path is ONLY required for sudo usage
# Rather than detect if sudo wants a password this time, -k makes
if tmp_path is None :
# sudo always ask for a password if one is required. The "--"
raise Exception ( " expecting tmp_path " )
# tells sudo that this is the end of sudo options and the command
r = random . randint ( 0 , 99999 )
# follows. Passing a quoted compound command to sudo (or sudo -s )
# directly doesn't work, so we shellquote it with pipes.quote()
# invoke command using a new connection over sudo
# and pass the quoted string to the user's shell.
result_file = os . path . join ( tmp_path , " sudo_result. %s " % r )
sudocmd = ' sudo -k -- " $SHELL " -c ' + pipes . quote ( cmd )
self . ssh . close ( )
bufsize = 4096 # Could make this a Runner param if needed
ssh_sudo = self . _get_conn ( )
timeout_secs = self . runner . timeout # Reusing runner's TCP connect timeout as command progress timeout
sudo_chan = ssh_sudo . invoke_shell ( )
chan = self . ssh . get_transport ( ) . open_session ( )
sudo_chan. send ( " sudo -s \n " )
chan. settimeout ( timeout_secs )
chan . get_pty ( ) # Many sudo setups require a terminal
# FIXME: using sudo with a password adds more delay, someone may wish
# print "exec_command: " + sudocmd
# to optimize to see when the channel is actually ready
chan . exec_command ( sudocmd )
if self . runner . sudo_pass :
if self . runner . sudo_pass :
time . sleep ( 0.1 ) # this is conservative
while not chan . recv_ready ( ) :
sudo_chan . send ( " %s \n " % self . runner . sudo_pass )
time . sleep ( 0.25 )
time . sleep ( 0.1 )
sudo_output = chan . recv ( bufsize ) # Pull prompt, catch errors, eat sudo output
#print "exec_command: " + sudo_output
# to avoid ssh expect logic, redirect output to file and move the
#print "exec_command: sending password"
# file when we are done with it...
chan . sendall ( self . runner . sudo_pass + ' \n ' )
sudo_chan . send ( " ( %s > %s _pre 2>/dev/null ; mv %s _pre %s ) & \n " % ( cmd , result_file , result_file , result_file ) )
# FIXME: someone may wish to optimize to not background the launch, and tell when the command
stdin = chan . makefile ( ' wb ' , bufsize )
# returns, removing the time.sleep(1) here
stdout = chan . makefile ( ' rb ' , bufsize )
time . sleep ( 1 )
stderr = chan . makefile_stderr ( ' rb ' , bufsize )
sudo_chan . close ( )
return stdin , stdout , stderr
self . ssh = self . _get_conn ( )
# now load the results of the JSON execution...
# FIXME: really need some timeout logic here
# though it doesn't make since to use the SSH timeout or impose any particular
# limit. Upgrades welcome.
sftp = self . ssh . open_sftp ( )
while True :
# print "waiting on %s" % result_file
time . sleep ( 1 )
try :
sftp . stat ( result_file )
break
except IOError :
pass
sftp . close ( )
# TODO: see if there's a SFTP way to just get the file contents w/o saving
# to disk vs this hack...
stdin , stdout , stderr = self . ssh . exec_command ( " cat %s " % result_file )
return ( stdin , stdout , stderr )
def put_file ( self , in_path , out_path ) :
def put_file ( self , in_path , out_path ) :
''' transfer a file from local to remote '''
''' transfer a file from local to remote '''
@ -231,4 +211,3 @@ class LocalConnection(object):
''' terminate the connection; nothing to do here '''
''' terminate the connection; nothing to do here '''
pass
pass