Repository: ansible/ansible

Issue:
Ansible 2.1.0.0 fails on nodes with python 2.4.3 when using local connection
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

```
ansible 2.1.0.0
  config file = /home/vagrant/ansibox/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

N/A
##### OS / ENVIRONMENT

N/A
##### SUMMARY

Ansible fails to run on nodes with python 2.4.3 when using local connection.
##### STEPS TO REPRODUCE

`hosts`:

```
localhost ansible_python_interpreter=/usr/bin/python2.4
```

Launch a ping:

```
ansible -vvvv -i hosts --connection=local -m setup -a 'filter=ansible_hostname' localhost
```
##### EXPECTED RESULTS

Everything works.
##### ACTUAL RESULTS

```
Using /home/vagrant/ansibox/ansible.cfg as config file
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: vagrant
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1467372753.07-1455101672
41584 `" && echo ansible-tmp-1467372753.07-145510167241584="` echo $HOME/.ansible/tmp/ansible-tmp-1467372753.07-145
510167241584 `" ) && sleep 0'
<localhost> PUT /tmp/tmp_b9L2w TO /home/vagrant/.ansible/tmp/ansible-tmp-1467372753.07-145510167241584/setup
<localhost> EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python2.4 /home/v
agrant/.ansible/tmp/ansible-tmp-1467372753.07-145510167241584/setup && sleep 0'
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_ES9ZiU/ansible_module_setup.py", line 123, in ?
    from ansible.module_utils.basic import *
  File "/tmp/ansible_ES9ZiU/ansible_modlib.zip/ansible/__init__.py", line 4, in ?
  File "/tmp/ansible/lib/ansible/release.py", line 19
    from __future__ import (absolute_import, division, print_function)
SyntaxError: from __future__ imports must occur at the beginning of the file

localhost | FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_name": "setup"
    },
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_ES9ZiU/ansible_module_setup.py\", l
ine 123, in ?\n    from ansible.module_utils.basic import *\n  File \"/tmp/ansible_ES9ZiU/ansible_modlib.zip/ansibl
e/__init__.py\", line 4, in ?\n  File \"/tmp/ansible/lib/ansible/release.py\", line 19\n    from __future__ import
(absolute_import, division, print_function)\nSyntaxError: from __future__ imports must occur at the beginning of th
e file\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "parsed": false
}
```

Looks like it's a problem with python 2.4.3:

```
$ python2.4 -c 'from __future__ import print_function'
  File "<string>", line 1
SyntaxError: from __future__ imports must occur at the beginning of the file
```

Maybe python 2.4 is not supported anymore?



Discussion:
When launching with remote connection, it works as expected:

```
$ ansible -vvv -i hosts -m ping localhost
Using /home/vagrant/ansibox/ansible.cfg as config file
<localhost> ESTABLISH CONNECTION FOR USER: None on PORT 22 TO localhost
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1467372315.88-1399282577
18926 `" && echo ansible-tmp-1467372315.88-139928257718926="` echo $HOME/.ansible/tmp/ansible-tmp-1467372315.88-139
928257718926 `" ) && sleep 0'
<localhost> PUT /tmp/tmpW_O4Wq TO /home/vagrant/.ansible/tmp/ansible-tmp-1467372315.88-139928257718926/ping
<localhost> EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python2.4 /home/v
agrant/.ansible/tmp/ansible-tmp-1467372315.88-139928257718926/ping; rm -rf "/home/vagrant/.ansible/tmp/ansible-tmp-
1467372315.88-139928257718926/" > /dev/null 2>&1 && sleep 0'
localhost | SUCCESS => {
    "changed": false,
    "invocation": {
        "module_args": {
            "data": null
        },
        "module_name": "ping"
    },
    "ping": "pong"
}
```

When launching directly the EXEC command in a shell, it works:

```
$ /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python2.4 /home/vagrant/.ansible/tmp/ansible-tmp-1467372753.07-145510167241584/setup && sleep 0'

{"invocation": {"module_args": {"filter": "ansible_hostname", "gather_subset": ["all"], "fact_path": "/etc/ansible/facts.d"}}, "changed": false, "_ansible_verbose_override": true, "ansible_facts": {"ansible_hostname": "localhost"}}
```

Got it!
I'm using ansible with [rolespec ](https://github.com/nickjj/rolespec) that "install" it by using the `hacking/env-setup` script.

So the local ENV is referencing the ansible libs, and with local_connection it inherits the 

When launching the EXEC command in a `env-setup`ed shell, I have the error:

``` shell
$ . /tmp/ansible/hacking/env-setup
running egg_info
creating lib/ansible.egg-info
writing requirements to lib/ansible.egg-info/requires.txt
writing lib/ansible.egg-info/PKG-INFO
writing top-level names to lib/ansible.egg-info/top_level.txt
writing dependency_links to lib/ansible.egg-info/dependency_links.txt
writing manifest file 'lib/ansible.egg-info/SOURCES.txt'
reading manifest file 'lib/ansible.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
no previously-included directories found matching 'v2'
no previously-included directories found matching 'docsite'
no previously-included directories found matching 'ticket_stubs'
no previously-included directories found matching 'packaging'
no previously-included directories found matching 'test'
no previously-included directories found matching 'hacking'
warning: no previously-included files found matching 'lib/ansible/modules/core/.git*'
warning: no previously-included files found matching 'lib/ansible/modules/extras/.git*'
no previously-included directories found matching 'lib/ansible/modules/core/.git'
no previously-included directories found matching 'lib/ansible/modules/extras/.git'
writing manifest file 'lib/ansible.egg-info/SOURCES.txt'

Setting up Ansible to run out of checkout...

PATH=/tmp/ansible/bin:/usr/local/bin:/bin:/usr/bin:/home/vagrant/bin
PYTHONPATH=/tmp/ansible/lib:
MANPATH=/tmp/ansible/docs/man:

Remember, you may wish to specify your host file with -i

Done!

$ /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python2.4 /home/vagrant/.ansible/tmp/ansible-tmp-1467372753.07-145510167241584/setup && sleep 0'
Traceback (most recent call last):
  File "/tmp/ansible_GPuS2A/ansible_module_setup.py", line 123, in ?
    from ansible.module_utils.basic import *
  File "/tmp/ansible_GPuS2A/ansible_modlib.zip/ansible/__init__.py", line 4, in ?
  File "/tmp/ansible/lib/ansible/release.py", line 19
    from __future__ import (absolute_import, division, print_function)
SyntaxError: from __future__ imports must occur at the beginning of the file

$ unset PYTHONPATH
# It works again
$ /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python2.4 /home/vagrant/.ansible/tmp/ansible-tmp-1467372753.07-145510167241584/setup && sleep 0'

{"invocation": {"module_args": {"filter": "ansible_hostname", "gather_subset": ["all"], "fact_path": "/etc/ansible/facts.d"}}, "changed": false, "_ansible_verbose_override": true, "ansible_facts": {"ansible_hostname": "localhost"}}
```

release.py should only have a few variables defined inside of it so we probably can get away with not using the `from __future__` stuff in it but I'm not clear on how to replicate this to test.  local connection normally will use the same python interpreter as was used to invoke ansible itself.  Since ansible itself uses python2.6+ I'm not sure how your environment is setup to get this behaviour.  Could you give more information about that?

We use [rolespec](https://github.com/nickjj/rolespec) with a VM using python2.6 to launch ansible but declaring python2.4 as `ansible_python_interpreter` in `hosts` file because it will be our target (:cry:).
Rolespec use `--connection=local` and install ansible by using the `hacking/env-setup` script, so we get a `PYTHONPATH` env var.

The weird thing is that it loads the `release.py` from the installed ansible folder instead of the zip as it should accordingly to the code: https://github.com/ansible/ansible/blob/devel/lib/ansible/executor/module_common.py#L118

We use [rolespec](https://github.com/nickjj/rolespec) with a VM using python2.6 to launch ansible but declaring python2.4 as `ansible_python_interpreter` in `hosts` file because it will be our target (:cry:).
Rolespec use `--connection=local` and install ansible by using the `hacking/env-setup` script, so we get a `PYTHONPATH` env var.

The weird thing is that it loads the `release.py` from the installed ansible folder instead of the zip as it should accordingly to the code: https://github.com/ansible/ansible/blob/devel/lib/ansible/executor/module_common.py#L118


Resolving patch:
diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py
--- a/lib/ansible/executor/module_common.py
+++ b/lib/ansible/executor/module_common.py
@@ -596,7 +596,9 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
                     # Create the module zip data
                     zipoutput = BytesIO()
                     zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
-                    zf.writestr('ansible/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\ntry:\n    from ansible.release import __version__,__author__\nexcept ImportError:\n    __version__="' + to_bytes(__version__) + b'"\n    __author__="' + to_bytes(__author__) + b'"\n')
+                    ### Note: If we need to import from release.py first,
+                    ### remember to catch all exceptions: https://github.com/ansible/ansible/issues/16523
+                    zf.writestr('ansible/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\n__version__="' + to_bytes(__version__) + b'"\n__author__="' + to_bytes(__author__) + b'"\n')
                     zf.writestr('ansible/module_utils/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\n')
 
                     zf.writestr('ansible_module_%s.py' % module_name, module_data)


Test patch: