Repository: ansible/ansible

Issue:
pam_limits "TypeError: a bytes-like object is required, not str" in python3
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report


##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
pam_limits

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
`host_key_checking = False`  (ansible.cfg)
`ansible_python_interpreter=/usr/bin/python3` (inventory)
##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
```
Ubuntu 16.04 LTS amd64
Python 3.5.2
Python 2.7.12
```

##### SUMMARY
<!--- Explain the problem briefly -->


##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
`ansible-playbook -i hosts -k --ask-vault-pass --ask-sudo-pass setup-common.yml`
** by default, ansible used python2.7 as interpreter, no problems. **
this time I added `10.7.42.132 ansible_python_interpreter=/usr/bin/python3` in the inventory file, issue occured.
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: <system-control> configuring ulimit...
  pam_limits:
      domain: "{{ item.domain }}"
      limit_type: "{{ item.limit_type }}"
      limit_item: "{{ item.limit_item }}"
      value: "{{ item.value }}"
  with_items:
      - { domain: 'buildteam', limit_type: 'soft', limit_item: 'nofile', value: '524288' }
      - { domain: 'buildteam', limit_type: 'hard', limit_item: 'nofile', value: '524288' }
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
no error expected.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
```
failed: [10.7.42.132] (item={u'domain': u'buildteam', u'limit_item': u'nofile', u'value': u'524288', u'limit_type': u'soft'}) => {
    "failed": true, 
    "item": {
        "domain": "buildteam", 
        "limit_item": "nofile", 
        "limit_type": "soft", 
        "value": "524288"
    }, 
    "module_stderr": "OpenSSH_7.2p2 Ubuntu-4ubuntu2.1, OpenSSL 1.0.2g  1 Mar 2016\r\ndebug1: Reading configuration data /home/maxsolar/.ssh/config\r\ndebug1: /home/maxsolar/.ssh/config line 9: Applying options for 10.7.42.*\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 8486\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to 10.7.42.132 closed.\r\n", 
    "module_stdout": "\r\nTraceback (most recent call last):\r\n  File \"/tmp/ansible_nu6l4jt0/ansible_module_pam_limits.py\", line 292, in <module>\r\n    main()\r\n  File \"/tmp/ansible_nu6l4jt0/ansible_module_pam_limits.py\", line 185, in main\r\n    nf.write(line)\r\n  File \"/usr/lib/python3.5/tempfile.py\", line 622, in func_wrapper\r\n    return func(*args, **kwargs)\r\nTypeError: a bytes-like object is required, not 'str'\r\n", 
    "msg": "MODULE FAILURE", 
    "rc": 0
}
failed: [10.7.42.132] (item={u'domain': u'buildteam', u'limit_item': u'nofile', u'value': u'524288', u'limit_type': u'hard'}) => {
    "failed": true, 
    "item": {
        "domain": "buildteam", 
        "limit_item": "nofile", 
        "limit_type": "hard", 
        "value": "524288"
    }, 
    "module_stderr": "OpenSSH_7.2p2 Ubuntu-4ubuntu2.1, OpenSSL 1.0.2g  1 Mar 2016\r\ndebug1: Reading configuration data /home/maxsolar/.ssh/config\r\ndebug1: /home/maxsolar/.ssh/config line 9: Applying options for 10.7.42.*\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 8486\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to 10.7.42.132 closed.\r\n", 
    "module_stdout": "\r\nTraceback (most recent call last):\r\n  File \"/tmp/ansible_di9uwhf9/ansible_module_pam_limits.py\", line 292, in <module>\r\n    main()\r\n  File \"/tmp/ansible_di9uwhf9/ansible_module_pam_limits.py\", line 185, in main\r\n    nf.write(line)\r\n  File \"/usr/lib/python3.5/tempfile.py\", line 622, in func_wrapper\r\n    return func(*args, **kwargs)\r\nTypeError: a bytes-like object is required, not 'str'\r\n", 
    "msg": "MODULE FAILURE", 
    "rc": 0
}

```
<!--- Paste verbatim command output between quotes below -->
```
failed: [10.7.42.132] (item={u'domain': u'buildteam', u'limit_item': u'nofile', u'value': u'524288', u'limit_type': u'soft'}) => {"failed": true, "item": {"domain": "buildteam", "limit_item": "nofile", "limit_type": "soft", "value": "524288"}, "module_stderr": "Shared connection to 10.7.42.132 closed.\r\n", "module_stdout": "\r\nTraceback (most recent call last):\r\n  File \"/tmp/ansible_x3bydk4k/ansible_module_pam_limits.py\", line 292, in <module>\r\n    main()\r\n  File \"/tmp/ansible_x3bydk4k/ansible_module_pam_limits.py\", line 185, in main\r\n    nf.write(line)\r\n  File \"/usr/lib/python3.5/tempfile.py\", line 622, in func_wrapper\r\n    return func(*args, **kwargs)\r\nTypeError: a bytes-like object is required, not 'str'\r\n", "msg": "MODULE FAILURE", "rc": 0}
failed: [10.7.42.132] (item={u'domain': u'buildteam', u'limit_item': u'nofile', u'value': u'524288', u'limit_type': u'hard'}) => {"failed": true, "item": {"domain": "buildteam", "limit_item": "nofile", "limit_type": "hard", "value": "524288"}, "module_stderr": "Shared connection to 10.7.42.132 closed.\r\n", "module_stdout": "\r\nTraceback (most recent call last):\r\n  File \"/tmp/ansible_ovzv8__a/ansible_module_pam_limits.py\", line 292, in <module>\r\n    main()\r\n  File \"/tmp/ansible_ovzv8__a/ansible_module_pam_limits.py\", line 185, in main\r\n    nf.write(line)\r\n  File \"/usr/lib/python3.5/tempfile.py\", line 622, in func_wrapper\r\n    return func(*args, **kwargs)\r\nTypeError: a bytes-like object is required, not 'str'\r\n", "msg": "MODULE FAILURE", "rc": 0}

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/system/pam_limits.py b/lib/ansible/modules/system/pam_limits.py
--- a/lib/ansible/modules/system/pam_limits.py
+++ b/lib/ansible/modules/system/pam_limits.py
@@ -133,9 +133,12 @@
 
 import os
 import os.path
-import shutil
+import tempfile
 import re
 
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+
 
 def main():
 
@@ -192,15 +195,15 @@ def main():
     space_pattern = re.compile(r'\s+')
 
     message = ''
-    f = open (limits_conf, 'r')
+    f = open (limits_conf, 'rb')
     # Tempfile
-    nf = tempfile.NamedTemporaryFile()
+    nf = tempfile.NamedTemporaryFile(mode='w+')
 
     found = False
     new_value = value
 
     for line in f:
-
+        line = to_native(line, errors='surrogate_or_strict')
         if line.startswith('#'):
             nf.write(line)
             continue
@@ -305,8 +308,5 @@ def main():
     module.exit_json(**res_args)
 
 
-# import module snippets
-from ansible.module_utils.basic import *
-
 if __name__ == '__main__':
     main()


Test patch: