Allow to change executable (shell/interpreter) when using raw

This patch adds an optional 'executable=' option to the raw command line to override the default shell (/bin/sh), much like the shell module does.
pull/1798/head
Dag Wieers 12 years ago
parent 3d3deb9797
commit 846161a1a4

@ -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())

@ -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):
executable = None
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, return ReturnData(conn=conn,
result=self.runner._low_level_exec_command(conn, module_args.encode('utf-8'), tmp, sudoable=True) result=self.runner._low_level_exec_command(conn, module_args, tmp, sudoable=True, executable=executable)
) )

@ -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)

@ -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)

@ -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 = ''

@ -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:

@ -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 = ''

@ -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

Loading…
Cancel
Save