Merge pull request #1798 from dagwieers/raw-executable

Allow to change executable (shell/interpreter) when using raw
This commit is contained in:
Daniel Hokka Zakrisson 2013-01-08 08:35:08 -08:00
commit 4955587d8c
8 changed files with 51 additions and 24 deletions

View file

@ -438,11 +438,14 @@ class Runner(object):
# ***************************************************** # *****************************************************
def _low_level_exec_command(self, conn, cmd, tmp, sudoable=False): def _low_level_exec_command(self, conn, cmd, tmp, sudoable=False, executable=None):
''' execute a command string over SSH, return the output ''' ''' execute a command string over SSH, return the output '''
if not executable:
executable = '/bin/sh'
sudo_user = self.sudo_user sudo_user = self.sudo_user
rc, stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudo_user, sudoable=sudoable) rc, stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudo_user, sudoable=sudoable, executable=executable)
if type(stdout) not in [ str, unicode ]: if type(stdout) not in [ str, unicode ]:
out = ''.join(stdout.readlines()) out = ''.join(stdout.readlines())

View file

@ -34,7 +34,15 @@ class ActionModule(object):
self.runner = runner self.runner = runner
def run(self, conn, tmp, module_name, module_args, inject): def run(self, conn, tmp, module_name, module_args, inject):
return ReturnData(conn=conn, executable = None
result=self.runner._low_level_exec_command(conn, module_args.encode('utf-8'), tmp, sudoable=True) args = []
) for arg in module_args.split(' '):
if arg.startswith('executable='):
executable = '='.join(arg.split('=')[1:])
else:
args.append(arg)
module_args = ' '.join(args).encode('utf-8')
return ReturnData(conn=conn,
result=self.runner._low_level_exec_command(conn, module_args, tmp, sudoable=True, executable=executable)
)

View file

@ -67,7 +67,7 @@ class Connection(object):
return self return self
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False): def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh'):
''' run a command on the remote host ''' ''' run a command on the remote host '''
vvv("EXEC COMMAND %s" % cmd) vvv("EXEC COMMAND %s" % cmd)
@ -79,6 +79,7 @@ class Connection(object):
mode='command', mode='command',
cmd=cmd, cmd=cmd,
tmp_path=tmp_path, tmp_path=tmp_path,
executable=executable,
) )
data = utils.jsonify(data) data = utils.jsonify(data)
data = utils.encrypt(self.key, data) data = utils.encrypt(self.key, data)

View file

@ -17,6 +17,7 @@
import traceback import traceback
import os import os
import pipes
import shutil import shutil
import subprocess import subprocess
from ansible import errors from ansible import errors
@ -36,20 +37,22 @@ class Connection(object):
return self return self
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False): def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh'):
''' run a command on the local host ''' ''' run a command on the local host '''
if self.runner.sudo and sudoable: if not self.runner.sudo or not sudoable:
local_cmd = [ executable, '-c', cmd]
else:
if self.runner.sudo_pass: if self.runner.sudo_pass:
# NOTE: if someone wants to add sudo w/ password to the local connection type, they are welcome # NOTE: if someone wants to add sudo w/ password to the local connection type, they are welcome
# to do so. The primary usage of the local connection is for crontab and kickstart usage however # to do so. The primary usage of the local connection is for crontab and kickstart usage however
# so this doesn't seem to be a huge priority # so this doesn't seem to be a huge priority
raise errors.AnsibleError("sudo with password is presently only supported on the 'paramiko' (SSH) and native 'ssh' connection types") raise errors.AnsibleError("sudo with password is presently only supported on the 'paramiko' (SSH) and native 'ssh' connection types")
cmd = "sudo -u {0} -s {1}".format(sudo_user, cmd) sudocmd = "sudo -u %s -s %s -c %s" % (sudo_user, executable, cmd)
local_cmd = ['/bin/sh', '-c', sudocmd]
vvv("EXEC %s" % cmd, host=self.host) vvv("EXEC %s" % local_cmd, host=self.host)
basedir = self.runner.basedir p = subprocess.Popen(local_cmd, cwd=self.runner.basedir, executable=executable,
p = subprocess.Popen(cmd, cwd=basedir, shell=True, stdin=None,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
return (p.returncode, '', stdout, stderr) return (p.returncode, '', stdout, stderr)

View file

@ -96,7 +96,7 @@ class Connection(object):
return ssh return ssh
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False): def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh'):
''' run a command on the remote host ''' ''' run a command on the remote host '''
bufsize = 4096 bufsize = 4096
@ -110,7 +110,7 @@ class Connection(object):
chan.get_pty() chan.get_pty()
if not self.runner.sudo or not sudoable: if not self.runner.sudo or not sudoable:
quoted_command = '/bin/sh -c ' + pipes.quote(cmd) quoted_command = executable + ' -c ' + pipes.quote(cmd)
vvv("EXEC %s" % quoted_command, host=self.host) vvv("EXEC %s" % quoted_command, host=self.host)
chan.exec_command(quoted_command) chan.exec_command(quoted_command)
else: else:
@ -123,8 +123,8 @@ class Connection(object):
# the -p option. # the -p option.
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
prompt = '[sudo via ansible, key=%s] password: ' % randbits prompt = '[sudo via ansible, key=%s] password: ' % randbits
sudocmd = 'sudo -k && sudo -p "%s" -u %s /bin/sh -c %s' % ( sudocmd = 'sudo -k && sudo -p "%s" -u %s %s -c %s' % (
prompt, sudo_user, pipes.quote(cmd)) prompt, sudo_user, executable, pipes.quote(cmd))
shcmd = '/bin/sh -c ' + pipes.quote(sudocmd) shcmd = '/bin/sh -c ' + pipes.quote(sudocmd)
vvv("EXEC %s" % shcmd, host=self.host) vvv("EXEC %s" % shcmd, host=self.host)
sudo_output = '' sudo_output = ''

View file

@ -81,13 +81,15 @@ class Connection(object):
os.write(self.wfd, "%s\n" % self.runner.remote_pass) os.write(self.wfd, "%s\n" % self.runner.remote_pass)
os.close(self.wfd) os.close(self.wfd)
def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False): def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh'):
''' run a command on the remote host ''' ''' run a command on the remote host '''
ssh_cmd = self._password_cmd() ssh_cmd = self._password_cmd()
ssh_cmd += ["ssh", "-tt", "-q"] + self.common_args + [self.host] ssh_cmd += ["ssh", "-tt", "-q"] + self.common_args + [self.host]
if self.runner.sudo and sudoable: if not self.runner.sudo or not sudoable:
ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd))
else:
# Rather than detect if sudo wants a password this time, -k makes # Rather than detect if sudo wants a password this time, -k makes
# sudo always ask for a password if one is required. # sudo always ask for a password if one is required.
# Passing a quoted compound command to sudo (or sudo -s) # Passing a quoted compound command to sudo (or sudo -s)
@ -97,10 +99,9 @@ class Connection(object):
# the -p option. # the -p option.
randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32))
prompt = '[sudo via ansible, key=%s] password: ' % randbits prompt = '[sudo via ansible, key=%s] password: ' % randbits
sudocmd = 'sudo -k && sudo -p "%s" -u %s /bin/sh -c %s' % ( sudocmd = 'sudo -k && sudo -p "%s" -u %s %s -c %s' % (
prompt, sudo_user, pipes.quote(cmd)) prompt, sudo_user, executable, pipes.quote(cmd))
cmd = sudocmd ssh_cmd.append('/bin/sh -c ' + pipes.quote(sudocmd))
ssh_cmd.append('/bin/sh -c ' + pipes.quote(cmd))
vvv("EXEC %s" % ssh_cmd, host=self.host) vvv("EXEC %s" % ssh_cmd, host=self.host)
try: try:

View file

@ -149,9 +149,11 @@ def command(data):
return dict(failed=True, msg='internal error: cmd is required') return dict(failed=True, msg='internal error: cmd is required')
if 'tmp_path' not in data: if 'tmp_path' not in data:
return dict(failed=True, msg='internal error: tmp_path is required') return dict(failed=True, msg='internal error: tmp_path is required')
if 'executable' not in data:
return dict(failed=True, msg='internal error: executable is required')
log("executing: %s" % data['cmd']) log("executing: %s" % data['cmd'])
p = subprocess.Popen(data['cmd'], shell=True, stdout=subprocess.PIPE, close_fds=True) p = subprocess.Popen(data['cmd'], executable=data['executable'], shell=True, stdout=subprocess.PIPE, close_fds=True)
(stdout, stderr) = p.communicate() (stdout, stderr) = p.communicate()
if stdout is None: if stdout is None:
stdout = '' stdout = ''

View file

@ -4,7 +4,16 @@ DOCUMENTATION = '''
--- ---
module: raw module: raw
short_description: Executes a low-down and dirty SSH command short_description: Executes a low-down and dirty SSH command
options: {} options:
free_form:
description:
- the raw module takes a free form command to run
required: true
executable:
description:
- change the shell used to execute the command. Should be an absolute path to the executable.
required: false
version_added: "1.0"
description: description:
- Executes a low-down and dirty SSH command, not going through the module - Executes a low-down and dirty SSH command, not going through the module
subsystem. This is useful and should only be done in two cases. The subsystem. This is useful and should only be done in two cases. The