Repository: ansible/ansible

Issue:
Modules failed version comparaison with Python 3.6
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
hostname

##### ANSIBLE VERSION
```
ansible 2.4.2.0
  config file = /home/user/git/nomnom.sh/poop/ansible.cfg
  configured module search path = [u'/home/user/git/nomnom.sh/poop/library', u'/home/user/.local/share/poop/library', u'/usr/share/ansible/library']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.14 (default, Jan  5 2018, 10:41:29) [GCC 7.2.1 20171224]
```

##### CONFIGURATION
```
ALLOW_WORLD_READABLE_TMPFILES(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
ANSIBLE_NOCOWS(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
ANSIBLE_PIPELINING(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
ANSIBLE_SSH_CONTROL_PATH_DIR(/home/user/git/nomnom.sh/poop/ansible.cfg) = /run/user/1000/ansible/ssh/
COMMAND_WARNINGS(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
DEFAULT_ALLOW_UNSAFE_LOOKUPS(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
DEFAULT_BECOME(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
DEFAULT_CALLBACK_PLUGIN_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/callback_plugins', u'/home/user/.local/share/poop/callback_plugins', u'/usr/share/ansible/plugins/callback']
DEFAULT_CONNECTION_PLUGIN_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/connection_plugins', u'/home/user/.local/share/poop/connection_plugins', u'/usr/share/ansible/plugins/connection']
DEFAULT_FILTER_PLUGIN_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/filter_plugins', u'/home/user/.local/share/poop/filter_plugins', u'/usr/share/ansible/plugins/filter']
DEFAULT_GATHERING(/home/user/git/nomnom.sh/poop/ansible.cfg) = explicit
DEFAULT_HOST_LIST(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/inventory']
DEFAULT_LOAD_CALLBACK_PLUGINS(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
DEFAULT_LOCAL_TMP(/home/user/git/nomnom.sh/poop/ansible.cfg) = /run/user/1000/ansible/tmp/
DEFAULT_LOG_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = /home/user/git/nomnom.sh/poop/logs/ansible.log
DEFAULT_LOOKUP_PLUGIN_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/lookup_plugins', u'/home/user/.local/share/poop/lookup_plugins', u'/usr/share/ansible/plugins/lookup']
DEFAULT_MANAGED_STR(/home/user/git/nomnom.sh/poop/ansible.cfg) = Ansible managed
DEFAULT_MODULE_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/library', u'/home/user/.local/share/poop/library', u'/usr/share/ansible/library']
DEFAULT_REMOTE_TMP(/home/user/git/nomnom.sh/poop/ansible.cfg) = /dev/shm/user/ansible/tmp/
DEFAULT_REMOTE_USER(/home/user/git/nomnom.sh/poop/ansible.cfg) = root
DEFAULT_ROLES_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/roles', u'/home/user/.local/share/poop/roles', u'/etc/ansible/roles']
DEFAULT_SCP_IF_SSH(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
DEFAULT_TIMEOUT(/home/user/git/nomnom.sh/poop/ansible.cfg) = 5
DEFAULT_TRANSPORT(/home/user/git/nomnom.sh/poop/ansible.cfg) = ssh
DEFAULT_UNDEFINED_VAR_BEHAVIOR(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
DEFAULT_VARS_PLUGIN_PATH(/home/user/git/nomnom.sh/poop/ansible.cfg) = [u'/home/user/git/nomnom.sh/poop/vars_plugins', u'/home/user/.local/share/poop/vars_plugins', u'/usr/share/ansible/plugins/vars']
DEPRECATION_WARNINGS(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
DISPLAY_SKIPPED_HOSTS(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
HOST_KEY_CHECKING(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
RETRY_FILES_ENABLED(/home/user/git/nomnom.sh/poop/ansible.cfg) = False
SYSTEM_WARNINGS(/home/user/git/nomnom.sh/poop/ansible.cfg) = True
```

##### OS / ENVIRONMENT
  * Running: Linux Arch / Linux
  * Managing: Linux Arch / Linux

##### SUMMARY
With python 3.6 on the managed node raise some error while using hostname module.

##### STEPS TO REPRODUCE
```
- name: Set node hostname
  hostname:
    name: '{{ inventory_hostname_short }}'
  tags: 'roles:nss:set_hostname'
```

With python2 set with `ansible_python_interpreter: /usr/bin/python2`:
```
$ ANSIBLE_CONFIG=~/git/nomnom.sh/poop/ansible.cfg ansible-playbook -i ~/git/nomnom.sh/poop/inventory/hosts -l loulou.nomnom.sh -c local  ~/.local/share/poop/playbooks/site.yml --tags 'roles:nss:set_hostname' --check --diff
TASK [nss : Set node hostname] **************************************************************************************************************************************************************************************************************************************************
ok: [loulou.nomnom.sh]
```

With python3 set with `ansible_python_interpreter: /usr/bin/python`:
```
$ ANSIBLE_CONFIG=~/git/nomnom.sh/poop/ansible.cfg ansible-playbook -i ~/git/nomnom.sh/poop/inventory/hosts -l loulou.nomnom.sh -c local  ~/.local/share/poop/playbooks/site.yml --tags 'roles:nss:set_hostname' --check --diff
TASK [nss : Set node hostname] **************************************************************************************************************************************************************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: '<' not supported between instances of 'int' and 'str'
fatal: [loulou.nomnom.sh]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last):\n  File \"/dev/shm/root/ansible_v19a8_v1/ansible_module_hostname.py\", line 589, in <module>\n    class SLESHostname(Hostname):\n  File \"/dev/shm/root/ansible_v19a8_v1/ansible_module_hostname.py\", line 593, in SLESHostname\n    if distribution_version and LooseVersion(\"10\") <= LooseVersion(distribution_version) <= LooseVersion(\"12\"):\n  File \"/usr/lib/python3.6/distutils/version.py\", line 58, in __le__\n    c = self._cmp(other)\n  File \"/usr/lib/python3.6/distutils/version.py\", line 337, in _cmp\n    if self.version < other.version:\nTypeError: '<' not supported between instances of 'int' and 'str'\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 1}
```
I guess this is very looks like #35417 or #32301 but eventually not affected by patch provided in #34427. As I understand this is a [python bug](https://bugs.python.org/issue14894) but no one in that community seems to handle the problem?

As I'm not fully understand the bug under that, I've looked at modyles relying on LooseVersion, are they potential impacted by this bug?


Discussion:
Files identified in the description:
* [lib/ansible/modules/system/hostname.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/hostname.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @alikins @saito-hideki
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@bobsaintcool Thanks for reporting this issue.
@bobsaintcool we decided in the core irc meeting, that we would not perform a blanket fix for all modules that _may_ be impacted.  Instead, we decided to fix each module on a case by case basis.

Likely aiming for better solutions that using `distutils.version` without adding any extra dependencies.

It seems to me that in this particular module, we could sanitize the data we are getting back from `get_distribution_version` and use standard integer comparison instead of using `LooseVersion`.
@sivel cannot attend the meeting, but happy to see what has been decided!

As I don't not really understand the bug behing "my playbook is broken under py3.6", do you have some links/doc that could help me it would be great :)

Thank by the way
I've assigned this for me to work on.  We are going to drop use of `LooseVersion` from the `hostname` module in favor of int casting and comparison, falling back to `UnimplementedStrategy`.

Resolving patch:
diff --git a/lib/ansible/modules/system/hostname.py b/lib/ansible/modules/system/hostname.py
--- a/lib/ansible/modules/system/hostname.py
+++ b/lib/ansible/modules/system/hostname.py
@@ -39,7 +39,6 @@
 import os
 import socket
 import traceback
-from distutils.version import LooseVersion
 
 from ansible.module_utils.basic import (
     AnsibleModule,
@@ -567,10 +566,14 @@ class FedoraHostname(Hostname):
 class SLESHostname(Hostname):
     platform = 'Linux'
     distribution = 'Suse linux enterprise server '
-    distribution_version = get_distribution_version()
-    if distribution_version and LooseVersion("10") <= LooseVersion(distribution_version) <= LooseVersion("12"):
-        strategy_class = SLESStrategy
-    else:
+    try:
+        distribution_version = get_distribution_version()
+        # cast to float may raise ValueError on non SLES, we use float for a little more safety over int
+        if distribution_version and 10 <= float(distribution_version) <= 12:
+            strategy_class = SLESStrategy
+        else:
+            raise ValueError()
+    except ValueError:
         strategy_class = UnimplementedStrategy
 
 


Test patch:
diff --git a/test/sanity/import/skip.txt b/test/sanity/import/skip.txt
--- a/test/sanity/import/skip.txt
+++ b/test/sanity/import/skip.txt
@@ -5,4 +5,3 @@ lib/ansible/modules/cloud/webfaction/webfaction_domain.py
 lib/ansible/modules/cloud/webfaction/webfaction_mailbox.py
 lib/ansible/modules/cloud/webfaction/webfaction_site.py
 lib/ansible/modules/packaging/os/yum_repository.py
-lib/ansible/modules/system/hostname.py