ansible: better emulate _low_level_execute_command()

Still needs a ton of work to emulate argument handling, shell selection,
and output emulation in every case. Unsurprisingly, Ansible documents
none of this.
wip-fakessh-exit-status
David Wilson 6 years ago
parent 5855f1739f
commit f3315fc172

@ -26,6 +26,8 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import json
import os
import pwd
import subprocess
import time
@ -112,19 +114,41 @@ def run_module(module, raw_params=None, args=None):
return json.dumps(e.dct)
def exec_command(cmd, in_data=''):
def get_user_shell():
"""
Run a command in subprocess, arranging for `in_data` to be supplied on its
standard input.
For commands executed directly via an SSH command-line, SSH looks up the
user's shell via getpwuid() and only defaults to /bin/sh if that field is
missing or empty.
"""
try:
pw_shell = pwd.getpwuid(os.geteuid()).pw_shell
except KeyError:
pw_shell = None
return pw_shell or '/bin/sh'
def exec_command(cmd, in_data='', chdir=None, shell=None):
"""
Run a command in a subprocess, emulating the argument handling behaviour of
SSH.
:param bytes cmd:
String command line, passed to user's shell.
:param bytes in_data:
Optional standard input for the command.
:return:
(return code, stdout bytes, stderr bytes)
"""
proc = subprocess.Popen(cmd,
assert isinstance(cmd, basestring)
proc = subprocess.Popen(
args=[get_user_shell(), '-c', cmd],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
shell=True)
cwd=chdir,
)
stdout, stderr = proc.communicate(in_data)
return proc.returncode, stdout, stderr

@ -26,6 +26,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
import commands
import os
import pwd
import shutil
@ -177,17 +178,24 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
def _low_level_execute_command(self, cmd, sudoable=True, in_data=None,
executable=None,
encoding_errors='surrogate_then_replace'):
encoding_errors='surrogate_then_replace',
chdir=None):
if executable is None: # executable defaults to False
executable = self._play_context.executable
if executable:
cmd = executable + ' -c ' + commands.mkarg(cmd)
# replaces 57 lines
# replaces 126 lines of make_become_cmd()
rc, stdout, stderr = self.call(
ansible_mitogen.helpers.exec_command,
cmd,
in_data,
cast(cmd),
cast(in_data),
chdir=cast(chdir),
)
return {
'rc': rc,
'stdout': to_text(stdout, encoding_errors),
'stdout_lines': '\n'.split(to_text(stdout, encoding_errors)),
'stdout': to_text(stdout, errors=encoding_errors),
'stdout_lines': to_text(stdout, errors=encoding_errors).splitlines(),
'stderr': stderr,
}

@ -0,0 +1,32 @@
---
# Verify the behaviour of _low_level_execute_command().
- hosts: all
gather_facts: false
tasks:
# "echo -en" to test we actually hit bash shell too.
- name: Run raw module without sudo
raw: 'echo -en $((1 + 1))'
register: raw
- name: Verify raw module output.
assert:
that:
- 'raw.rc == 0'
- 'raw.stdout_lines == ["2"]'
- 'raw.stdout == "2"'
- name: Run raw module with sudo
become: true
raw: 'whoami'
register: raw
# Can't test stdout because TTY inserts \r in Ansible version.
- debug: msg={{raw}}
- name: Verify raw module output.
assert:
that:
- 'raw.rc == 0'
- 'raw.stdout_lines == ["root"]'
Loading…
Cancel
Save