Repository: ansible/ansible-modules-core

Issue:
cron  module failing if entry already exists (on RHEL5)
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
cron module

##### ANSIBLE VERSION
```
ansible 2.3.0 (devel bb41a005b3) last updated 2016/11/17 15:58:57 (GMT +800)
  lib/ansible/modules/core: (detached HEAD a4cddac368) last updated 2016/11/17 15:59:31 (GMT +800)
  lib/ansible/modules/extras: (detached HEAD 19c7d5b31b) last updated 2016/11/17 16:00:00 (GMT +800)
  config file = 
  configured module search path = Default w/o overrides
```

##### OS / ENVIRONMENT
Red Hat Enterprise Linux Server release 6.4 (Santiago)

##### SUMMARY
cron module seems to fail if entry already exists

##### STEPS TO REPRODUCE
```
[ansible@vmhklftpscdv1 ~]$ cat cron.yml 
- hosts: "{{hosts}}"
  tasks:
      - name: test cron
        cron: name="Test Cron Module" minute="0" hour="*" job="cron.sh echo x "

[ansible@vmhklftpscdv1 ~]$ ansible-playbook cron.yml -e "hosts=vmfurlaposdbdv1"     

PLAY [vmfurlaposdbdv1] *************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************
ok: [vmfurlaposdbdv1]

TASK [test cron] *******************************************************************************************************************************
changed: [vmfurlaposdbdv1]

PLAY RECAP *************************************************************************************************************************************
vmfurlaposdbdv1            : ok=2    changed=1    unreachable=0    failed=0   

[ansible@vmhklftpscdv1 ~]$ ansible-playbook cron.yml -e "hosts=vmfurlaposdbdv1"

PLAY [vmfurlaposdbdv1] *************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************
ok: [vmfurlaposdbdv1]

TASK [test cron] *******************************************************************************************************************************
fatal: [vmfurlaposdbdv1]: FAILED! => {"changed": false, "failed": true, "module_stderr": "", "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_ERoh58/ansible_module_cron.py\", line 736, in ?\r\n    main()\r\n  File \"/tmp/ansible_ERoh58/ansible_module_cron.py\", line 693, in main\r\n    if not changed and not crontab.existing.endswith(('\\r', '\\n')):\r\nTypeError: expected a character buffer object\r\n", "msg": "MODULE FAILURE"}
        to retry, use: --limit @/home/ansible/cron.retry

PLAY RECAP *************************************************************************************************************************************
vmfurlaposdbdv1            : ok=1    changed=0    unreachable=0    failed=1   
```

On target machine you can see cron entry was created fine:
```
[oracle@vmfurlaposdbdv1 ~]$ crontab -l
PATH=/usr/local/bin

<SOME OTHER ENTRIES HERE>
#Ansible: Test Cron Module
0 * * * * cron.sh echo x 
```

##### EXPECTED RESULTS
Should do nothing as entry already exists.

##### ACTUAL RESULTS
Throws exception

Note seems related to managed host.  Host is running RHEL5.8.
Seems to run find on other managed hosts (most are running RHEL6)



Discussion:
<!-- @hkpatv: Thanks for filing this bug report! -->
@dsummersl, @EvanK, ping. This issue is waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: issue_bug_new --->
Ran with extra logging:
```
[ansible@vmhklftpscdv1 ~]$ ansible-playbook -vvvvvv cron.yml -e "hosts=vmfurlaposdbdv1"
No config file found; using defaults
Loading callback plugin default of type stdout, v2.0 from /usr/local/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: cron.yml *****************************************************************************************************************************
1 plays in cron.yml

PLAY [vmfurlaposdbdv1] *************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************
Using module file /usr/local/ansible/lib/ansible/modules/core/system/setup.py
<vmfurlaposdbdv1.gamma.lftltd.net> ESTABLISH CONNECTION FOR USER: oracle on PORT 22 TO vmfurlaposdbdv1.gamma.lftltd.net
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041 `" && echo ansible-tmp-1479788541.3-112979257772041="` echo $HOME/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041 `" ) && sleep 0'
<vmfurlaposdbdv1.gamma.lftltd.net> PUT /tmp/tmp6FIxVg TO /home/oracle/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041/setup.py
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c 'chmod u+x /home/oracle/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041/ /home/oracle/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041/setup.py && sleep 0'
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c '/usr/bin/python /home/oracle/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041/setup.py; rm -rf "/home/oracle/.ansible/tmp/ansible-tmp-1479788541.3-112979257772041/" > /dev/null 2>&1 && sleep 0'
ok: [vmfurlaposdbdv1]

TASK [test cron] *******************************************************************************************************************************
task path: /home/ansible/cron.yml:3
Using module file /usr/local/ansible/lib/ansible/modules/core/system/cron.py
<vmfurlaposdbdv1.gamma.lftltd.net> ESTABLISH CONNECTION FOR USER: oracle on PORT 22 TO vmfurlaposdbdv1.gamma.lftltd.net
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230 `" && echo ansible-tmp-1479788542.63-153314425000230="` echo $HOME/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230 `" ) && sleep 0'
<vmfurlaposdbdv1.gamma.lftltd.net> PUT /tmp/tmp6FIxVg TO /home/oracle/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230/cron.py
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c 'chmod u+x /home/oracle/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230/ /home/oracle/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230/cron.py && sleep 0'
<vmfurlaposdbdv1.gamma.lftltd.net> EXEC /bin/sh -c '/usr/bin/python /home/oracle/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230/cron.py; rm -rf "/home/oracle/.ansible/tmp/ansible-tmp-1479788542.63-153314425000230/" > /dev/null 2>&1 && sleep 0'
fatal: [vmfurlaposdbdv1]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_name": "cron"
    }, 
    "module_stderr": "", 
    "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_baxMTf/ansible_module_cron.py\", line 736, in ?\r\n    main()\r\n  File \"/tmp/ansible_baxMTf/ansible_module_cron.py\", line 693, in main\r\n    if not changed and not crontab.existing.endswith(('\\r', '\\n')):\r\nTypeError: expected a character buffer object\r\n", 
    "msg": "MODULE FAILURE"
}
        to retry, use: --limit @/home/ansible/cron.retry

PLAY RECAP *************************************************************************************************************************************
vmfurlaposdbdv1            : ok=1    changed=0    unreachable=0    failed=1   

[ansible@vmhklftpscdv1 ~]$ 
```
````
[oracle@vmfurlaposdbdv1 ansible-tmp-1479789011.48-210573686025636]$ cat test.py
x = 'XYZ'  
if not x.endswith(('\r', '\n')):
   print("Y")
[oracle@vmfurlaposdbdv1 ansible-tmp-1479789011.48-210573686025636]$ python test.py
Traceback (most recent call last):
  File "test.py", line 2, in ?
    if not x.endswith(('\r', '\n')):
TypeError: expected a character buffer object
[oracle@vmfurlaposdbdv1 ansible-tmp-1479789011.48-210573686025636]$ python -V
Python 2.4.3
[oracle@vmfurlaposdbdv1 ansible-tmp-1479789011.48-210573686025636]$ 
````
https://bytes.com/topic/python/answers/704087-startswith-prefix-start-end-query

That particular aspect of the functionality (the multiple
prefixes in a tuple) was only added Python 2.5. If you're
using <= 2.4 you'll need to use "or" or some other approach,
eg looping over a sequence of prefixes.
Change at line 693 in cron.py, and now working OK for me.  Probably a neater way to do it, but tested and it works for me.  Python 2.4 should be supported on target machines right?
```
# no changes to env/job, but existing crontab needs a terminating newline
#    if not changed and not crontab.existing.endswith(('\r', '\n')):
    if not changed and not crontab.existing.endswith('\r') and not crontab.existing.endswith('\n'):
        changed = True
```

Resolving patch:
diff --git a/system/cron.py b/system/cron.py
--- a/system/cron.py
+++ b/system/cron.py
@@ -717,8 +717,9 @@ def main():
                 changed = True
 
     # no changes to env/job, but existing crontab needs a terminating newline
-    if not changed and not crontab.existing.endswith(('\r', '\n')):
-        changed = True
+    if not changed:
+        if not (crontab.existing.endswith('\r') or crontab.existing.endswith('\n')):
+            changed = True
 
     res_args = dict(
         jobs = crontab.get_jobnames(),


Test patch: