Skip to content

Commit

Permalink
[stable-2.9] cron - Allow non-ascii (UTF-8) chars in cron file paths …
Browse files Browse the repository at this point in the history
…and jobs (#70426) (#71134)

* Encode/Decode files in UTF-8
* Use helper function in ansible
* Add an integration test
* Use emoji in test data.
* add changelog
* Also support non-ascii chars in filepath and add tests about this.
* Also use non-ascii chars in replaced text and ensure not to break cron syntax.
* rename self.existing to self.n_existing
* rename crontab.existing to crontab.n_existing.
(cherry picked from commit 5ce4764)

Co-authored-by: psi / Ryo Hirafuji <ryo.hirafuji@link-u.co.jp>

* try removing name references for state=absent

Signed-off-by: Rick Elrod <rick@elrod.me>

Co-authored-by: psi / Ryo Hirafuji <ryo.hirafuji@link-u.co.jp>
Co-authored-by: Rick Elrod <rick@elrod.me>
  • Loading branch information
3 people committed Aug 7, 2020
1 parent 0199b1c commit 523d0f5
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 13 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/70426-allow-non-ascii-chars-in-cron.yml
@@ -0,0 +1,2 @@
bugfixes:
- cron - encode and decode crontab files in UTF-8 explicitly to allow non-ascii chars in cron filepath and job (https://github.com/ansible/ansible/issues/69492)
28 changes: 15 additions & 13 deletions lib/ansible/modules/system/cron.py
Expand Up @@ -213,6 +213,7 @@
import tempfile

from ansible.module_utils.basic import AnsibleModule, get_platform
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible.module_utils.six.moves import shlex_quote


Expand All @@ -234,14 +235,16 @@ def __init__(self, module, user=None, cron_file=None):
self.root = (os.getuid() == 0)
self.lines = None
self.ansible = "#Ansible: "
self.existing = ''
self.n_existing = ''
self.cron_cmd = self.module.get_bin_path('crontab', required=True)

if cron_file:
if os.path.isabs(cron_file):
self.cron_file = cron_file
self.b_cron_file = to_bytes(cron_file, errors='surrogate_or_strict')
else:
self.cron_file = os.path.join('/etc/cron.d', cron_file)
self.b_cron_file = os.path.join(b'/etc/cron.d', to_bytes(cron_file, errors='surrogate_or_strict'))
else:
self.cron_file = None

Expand All @@ -253,9 +256,8 @@ def read(self):
if self.cron_file:
# read the cronfile
try:
f = open(self.cron_file, 'r')
self.existing = f.read()
self.lines = self.existing.splitlines()
f = open(self.b_cron_file, 'rb')
self.n_existing = to_native(f.read(), errors='surrogate_or_strict')
f.close()
except IOError:
# cron file does not exist
Expand All @@ -269,7 +271,7 @@ 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
self.n_existing = out

lines = out.splitlines()
count = 0
Expand All @@ -280,7 +282,7 @@ def read(self):
self.lines.append(l)
else:
pattern = re.escape(l) + '[\r\n]?'
self.existing = re.sub(pattern, '', self.existing, 1)
self.n_existing = re.sub(pattern, '', self.n_existing, 1)
count += 1

def is_empty(self):
Expand All @@ -294,15 +296,15 @@ def write(self, backup_file=None):
Write the crontab to the system. Saves all information.
"""
if backup_file:
fileh = open(backup_file, 'w')
fileh = open(backup_file, 'wb')
elif self.cron_file:
fileh = open(self.cron_file, 'w')
fileh = open(self.b_cron_file, 'wb')
else:
filed, path = tempfile.mkstemp(prefix='crontab')
os.chmod(path, int('0644', 8))
fileh = os.fdopen(filed, 'w')
fileh = os.fdopen(filed, 'wb')

fileh.write(self.render())
fileh.write(to_bytes(self.render()))
fileh.close()

# return if making a backup
Expand Down Expand Up @@ -631,7 +633,7 @@ def main():

if module._diff:
diff = dict()
diff['before'] = crontab.existing
diff['before'] = crontab.n_existing
if crontab.cron_file:
diff['before_header'] = crontab.cron_file
else:
Expand Down Expand Up @@ -724,8 +726,8 @@ def main():
changed = True

# no changes to env/job, but existing crontab needs a terminating newline
if not changed and crontab.existing != '':
if not (crontab.existing.endswith('\r') or crontab.existing.endswith('\n')):
if not changed and crontab.n_existing != '':
if not (crontab.n_existing.endswith('\r') or crontab.n_existing.endswith('\n')):
changed = True

res_args = dict(
Expand Down
60 changes: 60 additions & 0 deletions test/integration/targets/cron/tasks/main.yml
Expand Up @@ -99,3 +99,63 @@

- assert:
that: remove_cron_file is not changed

- name: Allow non-ascii chars in job (#69492)
block:
- name: Cron file creation
cron:
cron_file: cron_filename
name: "cron job that contain non-ascii chars in job (これは日本語です; This is Japanese)"
job: 'echo "うどんは好きだがお化け👻は苦手である。"'
user: root

- name: "Ensure cron_file contains job string"
replace:
path: /etc/cron.d/cron_filename
regexp: "うどんは好きだがお化け👻は苦手である。"
replace: "それは機密情報🔓です。"
register: find_chars
failed_when: (find_chars is not changed) or (find_chars is failed)

- name: Cron file deletion
cron:
cron_file: cron_filename
state: absent

- name: Check file succesfull deletion
stat:
path: /etc/cron.d/cron_filename
register: cron_file_stats

- assert:
that: not cron_file_stats.stat.exists

- name: Allow non-ascii chars in cron_file (#69492)
block:
- name: Cron file creation with non-ascii filename (これは日本語です; This is Japanese)
cron:
cron_file: 'なせば大抵なんとかなる👊'
name: "integration test cron"
job: 'echo "Hello, ansible!"'
user: root

- name: Check file exists
stat:
path: "/etc/cron.d/なせば大抵なんとかなる👊"
register: cron_file_stats

- assert:
that: cron_file_stats.stat.exists

- name: Cron file deletion
cron:
cron_file: 'なせば大抵なんとかなる👊'
state: absent

- name: Check file succesfull deletion
stat:
path: "/etc/cron.d/なせば大抵なんとかなる👊"
register: cron_file_stats

- assert:
that: not cron_file_stats.stat.exists

0 comments on commit 523d0f5

Please sign in to comment.