2012-06-17 11:35:59 +00:00
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import os
import subprocess
import shlex
import pipes
import random
import select
import fcntl
2012-08-15 00:13:02 +00:00
import ansible . constants as C
2012-08-09 01:09:14 +00:00
from ansible . callbacks import vvv
2012-06-17 11:35:59 +00:00
from ansible import errors
2013-01-10 05:50:56 +00:00
from ansible import utils
2012-06-17 11:35:59 +00:00
2012-08-18 14:52:24 +00:00
class Connection ( object ) :
2012-06-17 11:35:59 +00:00
''' ssh based connections '''
def __init__ ( self , runner , host , port ) :
self . runner = runner
self . host = host
self . port = port
def connect ( self ) :
''' connect to the remote host '''
2012-07-15 16:29:53 +00:00
2012-08-11 21:33:34 +00:00
vvv ( " ESTABLISH CONNECTION FOR USER: %s " % self . runner . remote_user , host = self . host )
2012-06-26 18:59:42 +00:00
self . common_args = [ ]
2012-08-15 00:13:02 +00:00
extra_args = C . ANSIBLE_SSH_ARGS
2012-06-17 11:35:59 +00:00
if extra_args is not None :
self . common_args + = shlex . split ( extra_args )
2012-06-19 19:58:24 +00:00
else :
self . common_args + = [ " -o " , " ControlMaster=auto " ,
" -o " , " ControlPersist=60s " ,
" -o " , " ControlPath=/tmp/ansible-ssh- % h- % p- %r " ]
2012-06-26 18:59:42 +00:00
self . common_args + = [ " -o " , " StrictHostKeyChecking=no " ]
if self . port is not None :
self . common_args + = [ " -o " , " Port= %d " % ( self . port ) ]
if self . runner . private_key_file is not None :
self . common_args + = [ " -o " , " IdentityFile= " + self . runner . private_key_file ]
2012-11-27 09:51:35 +00:00
if self . runner . remote_pass :
self . common_args + = [ " -o " , " GSSAPIAuthentication=no " ,
" -o " , " PubkeyAuthentication=no " ]
else :
self . common_args + = [ " -o " , " KbdInteractiveAuthentication=no " ,
" -o " , " PasswordAuthentication=no " ]
2012-06-26 18:59:42 +00:00
self . common_args + = [ " -o " , " User= " + self . runner . remote_user ]
2012-06-17 11:35:59 +00:00
return self
2012-11-16 22:42:19 +00:00
def _password_cmd ( self ) :
if self . runner . remote_pass :
try :
p = subprocess . Popen ( [ " sshpass " ] , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
p . communicate ( )
except OSError :
raise errors . AnsibleError ( " to use -c ssh with passwords, you must install the sshpass program " )
( self . rfd , self . wfd ) = os . pipe ( )
return [ " sshpass " , " -d %d " % self . rfd ]
return [ ]
def _send_password ( self ) :
if self . runner . remote_pass :
os . close ( self . rfd )
os . write ( self . wfd , " %s \n " % self . runner . remote_pass )
os . close ( self . wfd )
2012-12-23 18:17:07 +00:00
def exec_command ( self , cmd , tmp_path , sudo_user , sudoable = False , executable = ' /bin/sh ' ) :
2012-06-17 11:35:59 +00:00
''' run a command on the remote host '''
2012-11-16 22:42:19 +00:00
ssh_cmd = self . _password_cmd ( )
ssh_cmd + = [ " ssh " , " -tt " , " -q " ] + self . common_args + [ self . host ]
2012-12-23 18:17:07 +00:00
if not self . runner . sudo or not sudoable :
2013-01-08 16:45:37 +00:00
if executable :
ssh_cmd . append ( executable + ' -c ' + pipes . quote ( cmd ) )
else :
ssh_cmd . append ( cmd )
2012-12-23 18:17:07 +00:00
else :
2013-01-10 05:50:56 +00:00
sudocmd , prompt = utils . make_sudo_cmd ( sudo_user , executable , cmd )
ssh_cmd . append ( sudocmd )
2012-11-16 22:42:19 +00:00
vvv ( " EXEC %s " % ssh_cmd , host = self . host )
2012-11-23 16:52:55 +00:00
try :
# Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors
import pty
master , slave = pty . openpty ( )
p = subprocess . Popen ( ssh_cmd , stdin = slave ,
2012-12-23 00:44:00 +00:00
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2012-11-23 16:52:55 +00:00
stdin = os . fdopen ( master , ' w ' , 0 )
except :
p = subprocess . Popen ( ssh_cmd , stdin = subprocess . PIPE ,
2012-12-23 00:44:00 +00:00
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
2012-11-23 16:52:55 +00:00
stdin = p . stdin
2012-11-16 22:42:19 +00:00
self . _send_password ( )
if self . runner . sudo and sudoable and self . runner . sudo_pass :
fcntl . fcntl ( p . stdout , fcntl . F_SETFL ,
fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) | os . O_NONBLOCK )
2012-11-22 19:06:30 +00:00
sudo_output = ' '
2012-11-16 22:42:19 +00:00
while not sudo_output . endswith ( prompt ) :
rfd , wfd , efd = select . select ( [ p . stdout ] , [ ] ,
[ p . stdout ] , self . runner . timeout )
if p . stdout in rfd :
chunk = p . stdout . read ( )
if not chunk :
raise errors . AnsibleError ( ' ssh connection closed waiting for sudo password prompt ' )
sudo_output + = chunk
else :
stdout = p . communicate ( )
raise errors . AnsibleError ( ' ssh connection error waiting for sudo password prompt ' )
2012-11-23 16:52:55 +00:00
stdin . write ( self . runner . sudo_pass + ' \n ' )
2012-11-16 22:42:19 +00:00
fcntl . fcntl ( p . stdout , fcntl . F_SETFL , fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) & ~ os . O_NONBLOCK )
2012-06-20 18:22:48 +00:00
# We can't use p.communicate here because the ControlMaster may have stdout open as well
stdout = ' '
2012-12-23 00:44:00 +00:00
stderr = ' '
2012-09-27 19:44:34 +00:00
while True :
2012-12-23 00:44:00 +00:00
rfd , wfd , efd = select . select ( [ p . stdout , p . stderr ] , [ ] , [ p . stdout , p . stderr ] , 1 )
2012-06-20 18:22:48 +00:00
if p . stdout in rfd :
2012-09-27 19:44:34 +00:00
dat = os . read ( p . stdout . fileno ( ) , 9000 )
stdout + = dat
if dat == ' ' :
p . wait ( )
break
2012-12-23 00:44:00 +00:00
elif p . stderr in rfd :
dat = os . read ( p . stderr . fileno ( ) , 9000 )
stderr + = dat
if dat == ' ' :
p . wait ( )
break
2012-09-27 19:44:34 +00:00
elif p . poll ( ) is not None :
break
2012-11-23 16:52:55 +00:00
stdin . close ( ) # close stdin after we read from stdout (see also issue #848)
2012-06-20 18:22:48 +00:00
2012-12-23 00:44:00 +00:00
if p . returncode != 0 and stderr . find ( ' Bad configuration option: ControlPersist ' ) != - 1 :
2012-10-21 11:11:39 +00:00
raise errors . AnsibleError ( ' using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS= " " (or ansible_ssh_args in the config file) before running again ' )
2012-08-07 00:07:02 +00:00
2012-12-23 00:44:00 +00:00
return ( p . returncode , ' ' , stdout , stderr )
2012-06-17 11:35:59 +00:00
def put_file ( self , in_path , out_path ) :
''' transfer a file from local to remote '''
2012-08-09 01:09:14 +00:00
vvv ( " PUT %s TO %s " % ( in_path , out_path ) , host = self . host )
2012-06-17 11:35:59 +00:00
if not os . path . exists ( in_path ) :
raise errors . AnsibleFileNotFound ( " file or module does not exist: %s " % in_path )
2012-11-16 22:42:19 +00:00
cmd = self . _password_cmd ( )
2012-11-05 22:25:40 +00:00
if C . DEFAULT_SCP_IF_SSH :
2012-11-16 22:42:19 +00:00
cmd + = [ " scp " ] + self . common_args
cmd + = [ in_path , self . host + " : " + out_path ]
indata = None
2012-11-05 22:25:40 +00:00
else :
2012-11-16 22:42:19 +00:00
cmd + = [ " sftp " ] + self . common_args + [ self . host ]
indata = " put %s %s \n " % ( in_path , out_path )
p = subprocess . Popen ( cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
self . _send_password ( )
stdout , stderr = p . communicate ( indata )
2012-06-17 11:35:59 +00:00
if p . returncode != 0 :
raise errors . AnsibleError ( " failed to transfer file to %s : \n %s \n %s " % ( out_path , stdout , stderr ) )
def fetch_file ( self , in_path , out_path ) :
''' fetch a file from remote to local '''
2012-08-09 01:09:14 +00:00
vvv ( " FETCH %s TO %s " % ( in_path , out_path ) , host = self . host )
2012-11-16 22:42:19 +00:00
cmd = self . _password_cmd ( )
2012-11-05 22:25:40 +00:00
if C . DEFAULT_SCP_IF_SSH :
2012-11-16 22:42:19 +00:00
cmd + = [ " scp " ] + self . common_args
cmd + = [ self . host + " : " + in_path , out_path ]
indata = None
2012-11-05 22:25:40 +00:00
else :
2012-11-16 22:42:19 +00:00
cmd + = [ " sftp " ] + self . common_args + [ self . host ]
indata = " get %s %s \n " % ( in_path , out_path )
p = subprocess . Popen ( cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
self . _send_password ( )
stdout , stderr = p . communicate ( indata )
2012-06-17 11:35:59 +00:00
if p . returncode != 0 :
raise errors . AnsibleError ( " failed to transfer file from %s : \n %s \n %s " % ( in_path , stdout , stderr ) )
def close ( self ) :
2012-07-15 16:29:53 +00:00
''' not applicable since we ' re executing openssh binaries '''
2012-06-17 11:35:59 +00:00
pass
2012-07-15 16:29:53 +00:00