Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ansible_shell_executable is not used by default by module_utils/basic.py AnsibleModule run_command #24169

Closed
ericzolf opened this issue May 1, 2017 · 6 comments · Fixed by #24177 or #31361
Labels
affects_2.3 This issue/PR affects Ansible v2.3 bug This issue/PR relates to a bug. support:core This issue/PR relates to code supported by the Ansible Engineering Team.

Comments

@ericzolf
Copy link
Contributor

ericzolf commented May 1, 2017

ISSUE TYPE
  • Bug Report
COMPONENT NAME

core ?

ANSIBLE VERSION
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Jan 12 2017, 17:59:37) [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)]
CONFIGURATION
OS / ENVIRONMENT

N/A

SUMMARY

In order to manage "exotic" platforms (embedded Linux, Android, etc), one might need to set android_shell_executable in the inventory. This inventory variable isn't used as default by all modules relying on shell (setup, shell, and possibly others), so that they fail by default with an unclear 'file not found' error.
This is due to module_utils/basic.py AnsibleModule.run_command having the parameter executable empty by default, defaulting to /bin/sh, whereas it should IMHO default to the shell set by the user.
One can work around this issue where modules like shell offer the possibility to explicitly overwrite the shell used, but not with modules like setup, which don't offer this possibility.

STEPS TO REPRODUCE
  1. move away /bin/sh from your managed host
  2. set in your inventory ansible_shell_executable to the new shell location e.g. /opt/bin/sh
  3. call ansible -i my.inv -m setup managedhost or ansible -i my.inv -m shell -a id managedhost and they fail.
  4. Call ansible -i my.inv -m shell -a 'executable=/opt/bin/sh id' managedhost and it succeeds
EXPECTED RESULTS

The modules called use ansible_shell_executable and succeed.

ACTUAL RESULTS

The modules fail with a file not found error, which relates obscurely to /bin/sh not being found.

$ ansible -i ansidroid.inventory -m setup s4 -vvv
Using /etc/ansible/ansible.cfg as config file
META: ran handlers
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/setup.py                                                                              
<192.168.1.208> ESTABLISH SSH CONNECTION FOR USER: shell
<192.168.1.208> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=shell -o ConnectTimeout=10 -o ControlPath=/home/ericl/.ansible/cp/c950afe49b 192.168.1.208 '/system/xbin/bash -c '"'"'echo ~ && sleep 0'"'"''                                 
<192.168.1.208> (0, '/data/data/com.android.shell\n', '')
<192.168.1.208> ESTABLISH SSH CONNECTION FOR USER: shell
<192.168.1.208> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=shell -o ConnectTimeout=10 -o ControlPath=/home/ericl/.ansible/cp/c950afe49b 192.168.1.208 '/system/xbin/bash -c '"'"'( umask 77 && mkdir -p "` echo /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969 `" && echo ansible-tmp-1493649135.44-32469499696969="` echo /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969 `" ) && sleep 0'"'"''
<192.168.1.208> (0, 'ansible-tmp-1493649135.44-32469499696969=/data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969\n', '')
<192.168.1.208> PUT /tmp/tmpAQncVb TO /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/setup.py
<192.168.1.208> SSH: EXEC sftp -b - -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=shell -o ConnectTimeout=10 -o ControlPath=/home/ericl/.ansible/cp/c950afe49b '[192.168.1.208]'
<192.168.1.208> (0, 'sftp> put /tmp/tmpAQncVb /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/setup.py\n', '')
<192.168.1.208> ESTABLISH SSH CONNECTION FOR USER: shell
<192.168.1.208> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=shell -o ConnectTimeout=10 -o ControlPath=/home/ericl/.ansible/cp/c950afe49b 192.168.1.208 '/system/xbin/bash -c '"'"'chmod u+x /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/ /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/setup.py && sleep 0'"'"''
<192.168.1.208> (0, '', '')
<192.168.1.208> ESTABLISH SSH CONNECTION FOR USER: shell
<192.168.1.208> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=shell -o ConnectTimeout=10 -o ControlPath=/home/ericl/.ansible/cp/c950afe49b -tt 192.168.1.208 '/system/xbin/bash -c '"'"'/data/data/com.android.shell/python/bin/python /data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/setup.py; rm -rf "/data/data/com.android.shell/.ansible/tmp/ansible-tmp-1493649135.44-32469499696969/" > /dev/null 2>&1 && sleep 0'"'"''

<192.168.1.208> (0, '\r\n{"msg": "[Errno 2] No such file or directory", "failed": true, "cmd": "ps -p 1 -o \'comm|tail\' -n 1", "rc": 2, "invocation": {"module_args": {"filter": "*", "gather_subset": ["all"], "fact_path": "/etc/ansible/facts.d", "gather_timeout": 10}}}\r\n', 'Shared connection to 192.168.1.208 closed.\r\n')
s4 | FAILED! => {
    "changed": false, 
    "cmd": "ps -p 1 -o 'comm|tail' -n 1", 
    "failed": true, 
    "invocation": {
        "module_args": {
            "fact_path": "/etc/ansible/facts.d", 
            "filter": "*", 
            "gather_subset": [
                "all"
            ], 
            "gather_timeout": 10
        }
    }, 
    "msg": "[Errno 2] No such file or directory", 
    "rc": 2
}
@ansibot ansibot added affects_2.3 This issue/PR affects Ansible v2.3 bug_report needs_triage Needs a first human triage before being processed. labels May 1, 2017
@bcoca bcoca removed the needs_triage Needs a first human triage before being processed. label May 1, 2017
@bcoca
Copy link
Member

bcoca commented May 1, 2017

We don't pass the executable information to modules, not all of them execute commands.

For those that do, most use run_command w/o shell, the fact gathering module is one of the few exceptions that relies on shell semantics, but it does not explicitly set it, it relies on python's Popen and passes shell=True which leads to the execution of /bin/sh (which is expected to be present on all POSIX platforms).

@ericzolf
Copy link
Contributor Author

ericzolf commented May 1, 2017 via email

@bcoca
Copy link
Member

bcoca commented May 1, 2017

I was explaining the current state, I did not close this issue as we might want to revisit the current state of affairs.

The parameter is used mostly for 'controller side' construction of commands, we remotely create/copy/execute/remove files (which includes modules).

Looking at the Popen code it actually hard codes it args = ["/bin/sh", "-c"] ,

I'm thinking the simplest solution is for run_command to use $SHELL and override Popen with that when using shell=True, there would be no need to pass it as you are already 'using it' and executing the module under it.

@ericzolf
Copy link
Contributor Author

ericzolf commented May 2, 2017

Hi, I like the idea of using the underlying SHELL environment variable, it makes a lot of sense and should work. I haven't yet tested anything but I think that your solution keeps people from calling run_command with the executable parameter explicitly set (e.g. from the shell module, where they might use e.g. csh specific constructions). Based on your proposition, I was thinking about the following less intrusive change:

-    def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None,
+    def run_command(self, args, check_rc=False, close_fds=True, executable=os.environ.get('SHELL'), data=None, binary_data=False, path_prefix=None, cwd=None,
[...]
-        :kw executable: See documentation for subprocess.Popen(). Default None
+        :kw executable: See documentation for subprocess.Popen(). Default content of $SHELL or None

As said, not tested, and my python-skills are limited, but I think you get the idea.
Thank you very much for taking the point.

@bcoca
Copy link
Member

bcoca commented May 2, 2017

udpated the PR to account for existing executable

@abadger
Copy link
Contributor

abadger commented Oct 5, 2017

We had to revert this fix for 2.4.1 as it was changing the value of output, thus breaking existing user's playbooks. We're looking into a new fix for 2.5.x: #31361

@ansibot ansibot added bug This issue/PR relates to a bug. and removed bug_report labels Mar 7, 2018
@ansible ansible locked and limited conversation to collaborators Apr 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.3 This issue/PR affects Ansible v2.3 bug This issue/PR relates to a bug. support:core This issue/PR relates to code supported by the Ansible Engineering Team.
Projects
None yet
4 participants