diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 18f1e5c0db7..4d304266ccb 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -800,6 +800,7 @@ class AnsibleModule(object): self._debug = False self._diff = False self._socket_path = None + self._shell = None self._verbosity = 0 # May be used to set modifications to the environment for any # run_command invocation @@ -810,7 +811,7 @@ class AnsibleModule(object): self.aliases = {} self._legal_inputs = ['_ansible_check_mode', '_ansible_no_log', '_ansible_debug', '_ansible_diff', '_ansible_verbosity', '_ansible_selinux_special_fs', '_ansible_module_name', '_ansible_version', '_ansible_syslog_facility', - '_ansible_socket'] + '_ansible_socket', '_ansible_shell_executable'] self._options_context = list() if add_file_common_args: @@ -1612,6 +1613,9 @@ class AnsibleModule(object): elif k == '_ansible_socket': self._socket_path = v + elif k == '_ansible_shell_executable' and v: + self._shell = v + elif check_invalid_arguments and k not in legal_inputs: unsupported_parameters.add(k) @@ -2602,14 +2606,27 @@ class AnsibleModule(object): strings on python3, use encoding=None to turn decoding to text off. ''' - if isinstance(args, list): - if use_unsafe_shell: + if not isinstance(args, (list, binary_type, text_type)): + msg = "Argument 'args' to run_command must be list or string" + self.fail_json(rc=257, cmd=args, msg=msg) + + shell = False + if use_unsafe_shell: + + # stringify args for unsafe/direct shell usage + if isinstance(args, list): args = " ".join([shlex_quote(x) for x in args]) - shell = True - elif isinstance(args, (binary_type, text_type)): - if use_unsafe_shell: - shell = True + + # not set explicitly, check if set by controller + if executable: + args = [executable, '-c', args] + elif self._shell not in (None, '/bin/sh'): + args = [self._shell, '-c', args] else: + shell = True + else: + # ensure args are a list + if isinstance(args, (binary_type, text_type)): # On python2.6 and below, shlex has problems with text type # On python3, shlex needs a text type. if PY2: @@ -2617,18 +2634,9 @@ class AnsibleModule(object): elif PY3: args = to_text(args, errors='surrogateescape') args = shlex.split(args) - else: - msg = "Argument 'args' to run_command must be list or string" - self.fail_json(rc=257, cmd=args, msg=msg) - shell = False - if use_unsafe_shell: - if executable is None: - executable = os.environ.get('SHELL') - if executable: - args = [executable, '-c', args] - else: - shell = True + # expand shellisms + args = [os.path.expanduser(os.path.expandvars(x)) for x in args if x is not None] prompt_re = None if prompt_regex: @@ -2642,10 +2650,6 @@ class AnsibleModule(object): except re.error: self.fail_json(msg="invalid prompt regular expression given to run_command") - # expand things like $HOME and ~ - if not shell: - args = [os.path.expanduser(os.path.expandvars(x)) for x in args if x is not None] - rc = 0 msg = None st_in = None diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index d1241a9f994..308f0797b0d 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -607,6 +607,9 @@ class ActionBase(with_metaclass(ABCMeta, object)): # give the module the socket for persistent connections module_args['_ansible_socket'] = task_vars.get('ansible_socket') + # make sure all commands use the designated shell executable + module_args['_ansible_shell_executable'] = self._play_context.executable + def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=True, wrap_async=False): ''' Transfer and run a module along with its arguments.