Repository: ansible/ansible

Issue:
jenkins_plugin module doesn't work with python3 interpreter
##### ISSUE TYPE

 - Bug Report

##### COMPONENT NAME

`jenkins_plugin`

##### ANSIBLE VERSION

```
ansible 2.3.0.0
```

##### CONFIGURATION

```
ansible_python_interpreter=/usr/bin/python3
```

##### OS / ENVIRONMENT

Host: macOS 10.12.4
Controlled server(s): Ubuntu 16.04 LTS (AWS 64-bit AMI)

##### SUMMARY

When I run `jenkins_plugin` tasks, I get the following error (once for each plugin I attempt to install):

```
TASK [geerlingguy.jenkins : Install Jenkins plugins.] ******************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: AttributeError: 'TypeError' object has no attribute 'message'
failed: [1.2.3.4] (item=git) => {"failed": true, "item": "git", "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 341, in _get_json_data\n    json_data = json.load(r)\n  File \"/usr/lib/python3.5/json/__init__.py\", line 268, in load\n    parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)\n  File \"/usr/lib/python3.5/json/__init__.py\", line 312, in loads\n    s.__class__.__name__))\nTypeError: the JSON object must be str, not 'bytes'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 823, in <module>\n    main()\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 802, in main\n    jp = JenkinsPlugin(module)\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 323, in __init__\n    if self._csrf_enabled():\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 331, in _csrf_enabled\n    \"%s/%s\" % (self.url, \"api/json\"), 'CSRF')\n  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 346, in _get_json_data\n    details=e.message)\nAttributeError: 'TypeError' object has no attribute 'message'\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 1}
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: AttributeError: 'TypeError' object has no attribute 'message'
```

Formatted more nicely:

```
Traceback (most recent call last):
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 341, in _get_json_data
    json_data = json.load(r)
  File \"/usr/lib/python3.5/json/__init__.py\", line 268, in load
    parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
  File \"/usr/lib/python3.5/json/__init__.py\", line 312, in loads
    s.__class__.__name__))
TypeError: the JSON object must be str, not 'bytes'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 823, in <module>
    main()
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 802, in main
    jp = JenkinsPlugin(module)
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 323, in __init__
    if self._csrf_enabled():
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 331, in _csrf_enabled
    \"%s/%s\" % (self.url, \"api/json\"), 'CSRF')
  File \"/tmp/ansible_42hz6qg4/ansible_module_jenkins_plugin.py\", line 346, in _get_json_data
    details=e.message)
AttributeError: 'TypeError' object has no attribute 'message'
```

##### STEPS TO REPRODUCE

Use a task like the following:

```yaml
- name: Install Jenkins plugins.
  jenkins_plugin:
    name: "{{ item }}"
    params:
      url_username: "{{ jenkins_admin_username }}"
      url_password: "{{ jenkins_admin_password }}"
    updates_expiration: "{{ jenkins_plugin_updates_expiration }}"
    url: "http://{{ jenkins_hostname }}:{{ jenkins_http_port }}{{ jenkins_url_prefix }}"
  with_items: "{{ jenkins_plugins }}"
  notify: restart jenkins
```

And run it against a system that only has Python 3 available (and set `ansible_python_interpreter=/usr/bin/python3` in the host inventory).

##### EXPECTED RESULTS

The `jenkins_plugin` module should install plugins successfully, and not show an error message.

##### ACTUAL RESULTS

See summary above.

##### Workaround

My workaround, for now, is to use the method of installing Python 2 listed in this post: [How to fix SSH errors when using Ansible with newer OSes like Ubuntu 16.04](https://www.jeffgeerling.com/blog/2017/how-fix-ssh-errors-when-using-ansible-newer-oses-ubuntu-1604)


Discussion:
cc @jtyr
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Thanks for reporting this, @geerlingguy. I will try to fix it over the weekend.

Resolving patch:
diff --git a/lib/ansible/modules/web_infrastructure/jenkins_plugin.py b/lib/ansible/modules/web_infrastructure/jenkins_plugin.py
--- a/lib/ansible/modules/web_infrastructure/jenkins_plugin.py
+++ b/lib/ansible/modules/web_infrastructure/jenkins_plugin.py
@@ -330,7 +330,12 @@ def _csrf_enabled(self):
         csrf_data = self._get_json_data(
             "%s/%s" % (self.url, "api/json"), 'CSRF')
 
-        return csrf_data["useCrumbs"]
+        try:
+            return csrf_data["useCrumbs"]
+        except:
+            self.module.fail_json(
+                msg="Required fields not found in the Crum response.",
+                details=csrf_data)
 
     def _get_json_data(self, url, what, **kwargs):
         # Get the JSON data
@@ -338,12 +343,12 @@ def _get_json_data(self, url, what, **kwargs):
 
         # Parse the JSON data
         try:
-            json_data = json.load(r)
+            json_data = json.loads(to_native(r.read()))
         except Exception:
             e = get_exception()
             self.module.fail_json(
                 msg="Cannot parse %s JSON data." % what,
-                details=e.message)
+                details=to_native(e))
 
         return json_data
 
@@ -366,7 +371,7 @@ def _get_url_data(
                 self.module.fail_json(msg=msg_status, details=info['msg'])
         except Exception:
             e = get_exception()
-            self.module.fail_json(msg=msg_exception, details=e.message)
+            self.module.fail_json(msg=msg_exception, details=to_native(e))
 
         return response
 


Test patch: