Repository: ansible/ansible

Issue:
No PATH in Environment results in "Permission denied" for tasks on localhost
##### Issue Type:

Bug Report
##### Ansible Version:

ansible 2.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = /usr/share/ansible
##### Environment:

Debian Jessie, but should not matter
##### Summary:

If $PATH is not set in the environment, ansible uses an empty string for `ansible_python_interpreter` for localhost. This leads to a task failure with "Permission denied" when running tasks on localhost.

(The problem did not appear with ansible 1.7.2 as shipped with Debian and only appeared when I switched to the development version)
##### Steps To Reproduce:

```
root@foo:/ # env -i HOME=/root ansible -m shell -a : localhost
```
##### Expected Results:

```
localhost | SUCCESS | rc=0 >>
```
##### Actual Results:

```
localhost | FAILED | rc=0 >>
/bin/sh: 1: /root/.ansible/tmp/ansible-tmp-1450293879.27-221034150901375/command: Permission denied
```

Here's the same command with PATH:

```
root@foo:/ # env -i HOME=/root PATH="/usr/sbin:/usr/bin:/sbin:/bin" ansible -m shell -a : localhost
localhost | SUCCESS | rc=0 >>
```

The reason for this is that in https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/__init__.py#L453 the python interpreter for localhost is set to `sys.executable`, which is empty, if PATH is empty:

```
root@foo:/ # python -c 'import sys; print(sys.executable)'
/usr/bin/python
root@foo:/ # env -i python -c 'import sys; print(sys.executable)'
```

The shebang for the shell module is then set to `#!` while preparing the module for execution, which leads to the `Permission denied` message. 

I spotted this, when running an ansible playbook from a remote host through `remctl`, which does not set PATH, so there is an actual use case where this problem might occur.

I could not find a better way to reliably determine the python interpreter than using `sys.executable`. So I would suggest to print a warning message and ask the user to set `ansible_python_interpreter` in the inventory if `sys.executable` is empty, because it is really not obvious that no PATH results in "Permission denied".



Discussion:
i cannot reproduce:

```
bcoca@erebos:~/work/ansible(devel) $ PATH="" /usr/bin/python -c 'import sys; print sys.executable'
/usr/bin/python
bcoca@erebos:~/work/ansible(devel) $ env -i  /usr/bin/python -c 'import sys; print sys.executable'
/usr/bin/python
bcoca@erebos:~/work/ansible(devel) $ env -i  python -c 'import sys; print sys.executable'
/usr/bin/python
```

When I execute that command it always returns nothing. Just tried it on some machines. I have no idea why it works for you and not for me.

```
% lsb_release -a
[...]
Description:    openSUSE 13.1 (Bottle) (x86_64)
% env -i  python -c 'import sys; print sys.executable'

% python --version
Python 2.7.6
```

and

```
% lsb_release -a
[...]
Description:    Debian GNU/Linux 8.2 (jessie)
% env -i  python -c 'import sys; print sys.executable'

% python --version
Python 2.7.9
```

and

```
% lsb_release -a
[...]
Description:    Scientific Linux release 6.7 (Carbon)

% env -i  python -c 'import sys; print sys.executable'

% python --version
Python 2.6.6
```

and the last one

```
% lsb_release -a
[...]
Description:    Ubuntu 12.04.5 LTS
% env -i python -c 'import sys; print sys.executable'

% python --version
Python 2.7.3
```

However, your other command with PATH="" works for me, too.

edit: 
It really depends, how it's called:

```
% env -i /usr/bin/python -c 'import sys; print sys.executable'
/usr/bin/python
% env -i /usr/bin/env python -c 'import sys; print sys.executable'

% env -i python -c 'import sys; print sys.executable'
```

/usr/bin/ansible has this as first line

```
#!/usr/bin/env python
```

edit 2:
And when I change that to 

```
#!/usr/bin/python
```

the Problem disappears.

I poked around in python's source to see how sys.executable is assigned. It seems to be in Python-2.7.11/Modules/getpath.c in calculate_path(). The behaviour with the empty string is intended, there's even a comment about it there:

```
/* If there is no slash in the argv0 path, then we have to               
 * assume python is on the user's $PATH, since there's no                
 * other way to find a directory to start the search from.  If           
 * $PATH isn't exported, you lose.                                       
 */   
```

Hi @knorchbold, just going through some old issues and it appears (based on your last comment) that this is somewhat expected behavior with python? Are you still seeing this problem occur with the latest devel branch or stable-2.1?

Yes, it's still there:

```
root@foo:/ # ansible --version
ansible 2.1.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/usr/share/ansible']
root@foo:/ # env -i HOME=/root ANSIBLE_KEEP_REMOTE_FILES=1 ansible -vvvv -m shell -a ls localhost
Using /etc/ansible/ansible.cfg as config file
Loaded callback minimal of type stdout, v2.0
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: root
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1467127909.0-217202088594291 `" && echo ansible-tmp-1467127909.0-217202088594291="` echo $HOME/.ansible/tmp/ansible-tmp-1467127909.0-217202088594291 `" ) && sleep 0'
<127.0.0.1> PUT /tmp/tmpYPX7CD TO /root/.ansible/tmp/ansible-tmp-1467127909.0-217202088594291/command
<127.0.0.1> EXEC /bin/sh -c 'LANG=C LC_ALL=C LC_MESSAGES=C  /root/.ansible/tmp/ansible-tmp-1467127909.0-217202088594291/command && sleep 0'
localhost | FAILED | rc=0 >>
MODULE FAILURE
root@foo:/ # head -n 1 /root/.ansible/tmp/ansible-tmp-1467127909.0-217202088594291/command 
#!
```

This is on Debian 8 with ansible installed via a deb-package generated with `make deb` from `stable-2.1`.

I think you could say it is expected behaviour and rarely appears in reality. However, I think printing a warning if `sys.executable` is empty for some reason would be nice to save some time for the next person, that wants to call ansible with some script or like in my case `remctl` which doesn't set $PATH.


Resolving patch:
diff --git a/lib/ansible/inventory/__init__.py b/lib/ansible/inventory/__init__.py
--- a/lib/ansible/inventory/__init__.py
+++ b/lib/ansible/inventory/__init__.py
@@ -479,7 +479,12 @@ def _create_implicit_localhost(self, pattern='localhost'):
         new_host.vars = self.get_host_vars(new_host)
         new_host.set_variable("ansible_connection", "local")
         if "ansible_python_interpreter" not in new_host.vars:
-            new_host.set_variable("ansible_python_interpreter", sys.executable)
+            py_interp = sys.executable
+            if not py_interp:
+                # sys.executable is not set in some cornercases.  #13585
+                display.warning('Unable to determine python interpreter from sys.executable. Using /usr/bin/python default. You can correct this by setting ansible_python_interpreter for localhost')
+                py_interp = '/usr/bin/python'
+            new_host.set_variable("ansible_python_interpreter", py_interp)
         self.get_group("ungrouped").add_host(new_host)
         return new_host
 


Test patch: