Repository: ansible/ansible-modules-core

Issue:
When using cron_file with cron module new line is not inserted after job entry
Ansible version: (OSX, 1.9.0.1)
Environment: (source) OSX,amazon-linux (destination) amazon-linux

When using cron module the job entered into the cron.d file is missing a newline at end of file (cron fails to run the job file is there is no new line at the end of file).

Example:

```
- name: cron run nightly
  cron: name="cron nightly" hour=0 minute=0 user=root job="/bin/echo 'nightly cron job has run' | /usr/bin/logger -t cron_nightly" cron_file=nightly
```

Result: /etc/cron.d/nightly

```
#Ansible: cron nightly
0 0 * * * root /bin/echo 'nightly cron job has run' | /usr/bin/logger -t cron_nightly
~ (eof no new line)
```

Workaround:
If I add \n to the job it will work

```
- name: cron run nightly
  cron: name="cron nightly" hour=0 minute=0 user=root job="/bin/echo 'nightly cron job has run' | /usr/bin/logger -t cron_nightly __&& \n__" cron_file=nightly
```



Discussion:
[module: system/cron.py]

@steven-vogogo -- thanks for this bug report and workaround suggestion.

@dsummersl @EvanK -- thanks in advance for taking a look at this bug report and for your ongoing work in maintaining this module.

Looks like it should be doing the right thing here...

https://github.com/ansible/ansible-modules-core/blob/devel/system/cron.py#L355-L358

Following up on this issue, I believe the link provided is not correcting the issue. When using a cron file there needs to be a blank line at the end of the file. Not sure if that requires \n\n possibly since in the crontab it only has single line entries.

with `ansible 2.0.2.0` using `xubuntu xenial` to configure the regular root crontab on `debian jessie`  the bug is still there.
Also a last line starting with an `#` is acceptable as well.

@dsummersl, @EvanK, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/jctanner/ansibullbot/blob/ISSUE_TRIAGE_CLASS/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->


Resolving patch:
diff --git a/system/cron.py b/system/cron.py
--- a/system/cron.py
+++ b/system/cron.py
@@ -223,6 +223,7 @@ def __init__(self, module, user=None, cron_file=None):
         self.root      = (os.getuid() == 0)
         self.lines     = None
         self.ansible   = "#Ansible: "
+        self.existing  = ''
 
         if cron_file:
             if os.path.isabs(cron_file):
@@ -241,7 +242,8 @@ def read(self):
             # read the cronfile
             try:
                 f = open(self.cron_file, 'r')
-                self.lines = f.read().splitlines()
+                self.existing = f.read()
+                self.lines = self.existing.splitlines()
                 f.close()
             except IOError:
                 # cron file does not exist
@@ -255,6 +257,8 @@ def read(self):
             if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
                 raise CronTabError("Unable to read crontab")
 
+            self.existing = out
+
             lines = out.splitlines()
             count = 0
             for l in lines:
@@ -262,6 +266,9 @@ def read(self):
                                  not re.match( r'# \(/tmp/.*installed on.*\)', l) and
                                  not re.match( r'# \(.*version.*\)', l)):
                     self.lines.append(l)
+                else:
+                    pattern = re.escape(l) + '[\r\n]?'
+                    self.existing = re.sub(pattern, '', self.existing, 1)
                 count += 1
 
     def is_empty(self):
@@ -460,8 +467,8 @@ def render(self):
             crons.append(cron)
 
         result = '\n'.join(crons)
-        if result and result[-1] not in ['\n', '\r']:
-            result += '\n'
+        if result:
+            result = result.rstrip('\r\n') + '\n'
         return result
 
     def _read_user_execute(self):
@@ -577,7 +584,7 @@ def main():
 
     if module._diff:
         diff = dict()
-        diff['before'] = crontab.render()
+        diff['before'] = crontab.existing
         if crontab.cron_file:
             diff['before_header'] = crontab.cron_file
         else:
@@ -656,6 +663,10 @@ def main():
                 crontab.remove_job(name)
                 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
+
     res_args = dict(
         jobs = crontab.get_jobnames(),
         envs = crontab.get_envnames(),


Test patch: