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
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 ]
self . common_args + = [ " -o " , " User= " + self . runner . remote_user ]
2012-06-17 11:35:59 +00:00
return self
2012-07-15 15:54:39 +00:00
def exec_command ( self , cmd , tmp_path , sudo_user , sudoable = False ) :
2012-06-17 11:35:59 +00:00
''' run a command on the remote host '''
2012-06-26 18:59:42 +00:00
ssh_cmd = [ " ssh " , " -tt " , " -q " ] + self . common_args + [ self . host ]
2012-06-17 11:35:59 +00:00
if self . runner . sudo and sudoable :
2012-08-07 00:07:02 +00:00
# Rather than detect if sudo wants a password this time, -k makes
2012-08-11 14:57:04 +00:00
# sudo always ask for a password if one is required.
2012-08-10 04:15:23 +00:00
# Passing a quoted compound command to sudo (or sudo -s)
2012-08-07 00:07:02 +00:00
# directly doesn't work, so we shellquote it with pipes.quote()
2012-06-17 11:35:59 +00:00
# and pass the quoted string to the user's shell. We loop reading
# output until we see the randomly-generated sudo prompt set with
# the -p option.
randbits = ' ' . join ( chr ( random . randint ( ord ( ' a ' ) , ord ( ' z ' ) ) ) for x in xrange ( 32 ) )
prompt = ' [sudo via ansible, key= %s ] password: ' % randbits
2012-10-26 03:00:07 +00:00
sudocmd = ' sudo -k && sudo -p " %s " -u %s /bin/sh -c %s ' % (
2012-06-17 11:35:59 +00:00
prompt , sudo_user , pipes . quote ( cmd ) )
sudo_output = ' '
ssh_cmd . append ( sudocmd )
2012-08-09 01:09:14 +00:00
vvv ( " EXEC %s " % ssh_cmd , host = self . host )
2012-06-17 11:35:59 +00:00
p = subprocess . Popen ( ssh_cmd , stdin = subprocess . PIPE ,
2012-06-22 11:37:23 +00:00
stdout = subprocess . PIPE , stderr = subprocess . STDOUT )
2012-06-17 11:35:59 +00:00
if self . runner . sudo_pass :
fcntl . fcntl ( p . stdout , fcntl . F_SETFL ,
fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) | os . O_NONBLOCK )
while not sudo_output . endswith ( prompt ) :
2012-06-19 19:55:00 +00:00
rfd , wfd , efd = select . select ( [ p . stdout ] , [ ] ,
[ p . stdout ] , self . runner . timeout )
2012-06-17 11:35:59 +00:00
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 :
2012-06-22 11:37:23 +00:00
stdout = p . communicate ( )
2012-06-17 11:35:59 +00:00
raise errors . AnsibleError ( ' ssh connection error waiting for sudo password prompt ' )
p . stdin . write ( self . runner . sudo_pass + ' \n ' )
fcntl . fcntl ( p . stdout , fcntl . F_SETFL , fcntl . fcntl ( p . stdout , fcntl . F_GETFL ) & ~ os . O_NONBLOCK )
else :
ssh_cmd . append ( cmd )
2012-08-09 01:09:14 +00:00
vvv ( " EXEC %s " % ssh_cmd , host = self . host )
2012-06-17 11:35:59 +00:00
p = subprocess . Popen ( ssh_cmd , stdin = subprocess . PIPE ,
2012-06-22 11:37:23 +00:00
stdout = subprocess . PIPE , stderr = subprocess . STDOUT )
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 = ' '
while p . poll ( ) is None :
rfd , wfd , efd = select . select ( [ p . stdout ] , [ ] , [ p . stdout ] , 1 )
if p . stdout in rfd :
stdout + = os . read ( p . stdout . fileno ( ) , 1024 )
2012-08-11 13:13:07 +00:00
p . stdin . close ( ) # close stdin after we read from stdout (see also issue #848)
2012-06-20 18:22:48 +00:00
if p . returncode != 0 and stdout . 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-06-20 18:22:48 +00:00
return ( ' ' , stdout , ' ' )
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-06-26 18:59:42 +00:00
sftp_cmd = [ " sftp " ] + self . common_args + [ self . host ]
2012-06-17 11:35:59 +00:00
p = subprocess . Popen ( sftp_cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
stdout , stderr = p . communicate ( " put %s %s \n " % ( in_path , out_path ) )
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-06-26 18:59:42 +00:00
sftp_cmd = [ " sftp " ] + self . common_args + [ self . host ]
2012-06-17 11:35:59 +00:00
p = subprocess . Popen ( sftp_cmd , stdin = subprocess . PIPE ,
stdout = subprocess . PIPE , stderr = subprocess . PIPE )
stdout , stderr = p . communicate ( " get %s %s \n " % ( in_path , out_path ) )
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