diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 002625eda40..803fca68788 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -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 ''' + if not executable: + executable = '/bin/sh' + 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 ]: out = ''.join(stdout.readlines()) diff --git a/lib/ansible/runner/action_plugins/raw.py b/lib/ansible/runner/action_plugins/raw.py index c683ac64318..f1e8c65064b 100644 --- a/lib/ansible/runner/action_plugins/raw.py +++ b/lib/ansible/runner/action_plugins/raw.py @@ -34,7 +34,15 @@ class ActionModule(object): self.runner = runner 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, - 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) ) - diff --git a/lib/ansible/runner/connection_plugins/fireball.py b/lib/ansible/runner/connection_plugins/fireball.py index c21cf0aa44a..a126545f93a 100644 --- a/lib/ansible/runner/connection_plugins/fireball.py +++ b/lib/ansible/runner/connection_plugins/fireball.py @@ -67,7 +67,7 @@ class Connection(object): 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 ''' vvv("EXEC COMMAND %s" % cmd) @@ -79,6 +79,7 @@ class Connection(object): mode='command', cmd=cmd, tmp_path=tmp_path, + executable=executable, ) data = utils.jsonify(data) data = utils.encrypt(self.key, data) diff --git a/lib/ansible/runner/connection_plugins/local.py b/lib/ansible/runner/connection_plugins/local.py index c7d720fa91d..d1fae86114c 100644 --- a/lib/ansible/runner/connection_plugins/local.py +++ b/lib/ansible/runner/connection_plugins/local.py @@ -17,6 +17,7 @@ import traceback import os +import pipes import shutil import subprocess from ansible import errors @@ -36,20 +37,22 @@ class Connection(object): 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 ''' - 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: # 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 # 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") - 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) - basedir = self.runner.basedir - p = subprocess.Popen(cmd, cwd=basedir, shell=True, stdin=None, + vvv("EXEC %s" % local_cmd, host=self.host) + p = subprocess.Popen(local_cmd, cwd=self.runner.basedir, executable=executable, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() return (p.returncode, '', stdout, stderr) diff --git a/lib/ansible/runner/connection_plugins/paramiko_ssh.py b/lib/ansible/runner/connection_plugins/paramiko_ssh.py index 46482692893..84fb99e394a 100644 --- a/lib/ansible/runner/connection_plugins/paramiko_ssh.py +++ b/lib/ansible/runner/connection_plugins/paramiko_ssh.py @@ -96,7 +96,7 @@ class Connection(object): 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 ''' bufsize = 4096 @@ -110,7 +110,7 @@ class Connection(object): chan.get_pty() 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) chan.exec_command(quoted_command) else: @@ -123,8 +123,8 @@ class Connection(object): # 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 - sudocmd = 'sudo -k && sudo -p "%s" -u %s /bin/sh -c %s' % ( - prompt, sudo_user, pipes.quote(cmd)) + sudocmd = 'sudo -k && sudo -p "%s" -u %s %s -c %s' % ( + prompt, sudo_user, executable, pipes.quote(cmd)) shcmd = '/bin/sh -c ' + pipes.quote(sudocmd) vvv("EXEC %s" % shcmd, host=self.host) sudo_output = '' diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py index 6c85d68aed2..b8dc96245c7 100644 --- a/lib/ansible/runner/connection_plugins/ssh.py +++ b/lib/ansible/runner/connection_plugins/ssh.py @@ -81,13 +81,15 @@ class Connection(object): os.write(self.wfd, "%s\n" % self.runner.remote_pass) 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 ''' ssh_cmd = self._password_cmd() 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 # sudo always ask for a password if one is required. # Passing a quoted compound command to sudo (or sudo -s) @@ -97,10 +99,9 @@ class Connection(object): # 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 - sudocmd = 'sudo -k && sudo -p "%s" -u %s /bin/sh -c %s' % ( - prompt, sudo_user, pipes.quote(cmd)) - cmd = sudocmd - ssh_cmd.append('/bin/sh -c ' + pipes.quote(cmd)) + sudocmd = 'sudo -k && sudo -p "%s" -u %s %s -c %s' % ( + prompt, sudo_user, executable, pipes.quote(cmd)) + ssh_cmd.append('/bin/sh -c ' + pipes.quote(sudocmd)) vvv("EXEC %s" % ssh_cmd, host=self.host) try: diff --git a/library/fireball b/library/fireball index 88248d6b001..a3b09f45d86 100644 --- a/library/fireball +++ b/library/fireball @@ -149,9 +149,11 @@ def command(data): return dict(failed=True, msg='internal error: cmd is required') if 'tmp_path' not in data: 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']) - 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() if stdout is None: stdout = '' diff --git a/library/raw b/library/raw index 061d8eb066a..be81f97dae3 100644 --- a/library/raw +++ b/library/raw @@ -4,7 +4,16 @@ DOCUMENTATION = ''' --- module: raw 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: - 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