2
# -*- coding: utf-8 -*-
4
# (c) 2012, Dane Summers <dsummers@pinedesk.biz>
5
# (c) 2013, Mike Grozak <mike.grozak@gmail.com>
7
# This file is part of Ansible
9
# Ansible is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
14
# Ansible is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
22
# Cron Plugin: The goal of this plugin is to provide an indempotent method for
23
# setting up cron jobs on a host. The script will play well with other manually
24
# entered crons. Each cron job entered will be preceded with a comment
25
# describing the job so that it can be found later, which is required to be
26
# present in order for this plugin to find/modify the job.
31
short_description: Manage crontab entries.
33
- Use this module to manage crontab entries. This module allows you to create named
34
crontab entries, update, or delete them.
35
- 'The module includes one line with the description of the crontab entry C("#Ansible: <name>")
36
corresponding to the "name" passed to the module, which is used by future ansible/module calls
37
to find/check the state.'
42
- Description of a crontab entry.
48
- The specific user who's crontab should be modified.
54
- The command to execute.
55
- Required if state=present.
61
- Whether to ensure the job is present or absent.
67
- If specified, uses this file in cron.d versus in the main crontab
73
- If set, then create a backup of the crontab before it is modified.
74
- The location of the backup is returned in the C(backup) variable by this module.
80
- Minute when the job should run ( 0-59, *, */2, etc )
86
- Hour when the job should run ( 0-23, *, */2, etc )
92
- Day of the month the job should run ( 1-31, *, */2, etc )
98
- Month of the year the job should run ( 1-12, *, */2, etc )
104
- Day of the week that the job should run ( 0-7 for Sunday - Saturday, or mon, tue, * etc )
111
- If the job should be run at reboot, will ignore minute, hour, day, and month settings in favour of C(@reboot)
115
choices: [ "yes", "no" ]
119
- code: 'cron: name="check dirs" hour="5,2" job="ls -alh > /dev/null"'
120
description: Ensure a job that runs at 2 and 5 exists. Creates an entry like "* 5,2 * * ls -alh > /dev/null"
121
- code: 'cron: name="an old job" cron job="/some/dir/job.sh" state=absent'
122
description: 'Ensure an old job is no longer present. Removes any job that is preceded by "#Ansible: an old job" in the crontab'
123
- code: 'cron: name="a job for reboot" reboot=yes job="/some/job.sh"'
124
description: 'Creates an entry like "@reboot /some/job.sh"'
125
- code: 'cron: name="yum autoupdate" weekday="2" minute=0 hour=12 user="root" job="YUMINTERACTIVE=0 /usr/sbin/yum-autoupdate" cron_file=ansible_yum-autoupdate'
136
def get_jobs_file(module, user, tmpfile, cron_file):
138
cmd = "cp -fp /etc/cron.d/%s %s" % (cron_file, tmpfile)
140
cmd = "crontab -l %s > %s" % (user,tmpfile)
142
return module.run_command(cmd)
144
def install_jobs(module, user, tmpfile, cron_file):
146
cmd = "ln -f %s /etc/cron.d/%s" % (tmpfile, cron_file)
148
cmd = "crontab %s %s" % (user, tmpfile)
150
return module.run_command(cmd)
152
def get_jobs(tmpfile):
153
lines = open(tmpfile).read().splitlines()
157
if comment is not None:
158
jobs.append([comment,l])
160
elif re.match( r'#Ansible: ',l):
161
comment = re.sub( r'#Ansible: ', '', l)
164
def find_job(name,tmpfile):
165
jobs = get_jobs(tmpfile)
171
def add_job(module,name,job,tmpfile):
172
f = open(tmpfile, 'a')
173
f.write("#Ansible: %s\n%s\n" % (name, job))
176
def update_job(name,job,tmpfile):
177
return _update_job(name,job,tmpfile,do_add_job)
179
def do_add_job(lines, comment, job):
180
lines.append(comment)
183
def remove_job(name,tmpfile):
184
return _update_job(name, "", tmpfile, do_remove_job)
186
def do_remove_job(lines,comment,job):
189
def remove_job_file(cron_file):
190
fname = "/etc/cron.d/%s" % (cron_file)
193
def _update_job(name,job,tmpfile,addlinesfunction):
194
ansiblename = "#Ansible: %s" % (name)
196
lines = f.read().splitlines()
201
if comment is not None:
202
addlinesfunction(newlines,comment,job)
204
elif l == ansiblename:
208
f = open(tmpfile, 'w')
214
if len(newlines) == 0:
217
return False # TODO add some more error testing
219
def get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot):
222
return "@reboot %s %s" % (user, job)
224
return "@reboot %s" % (job)
227
return "%s %s %s %s %s %s %s" % (minute,hour,day,month,weekday,user,job)
229
return "%s %s %s %s %s %s" % (minute,hour,day,month,weekday,job)
234
# The following example playbooks:
235
# - action: cron name="check dirs" hour="5,2" job="ls -alh > /dev/null"
237
# action: name="do the job" cron hour="5,2" job="/some/dir/job.sh"
239
# action: name="an old job" cron job="/some/dir/job.sh" state=absent
242
# # Ansible: check dirs
243
# * * 5,2 * * ls -alh > /dev/null
244
# # Ansible: do the job
245
# * * 5,2 * * /some/dir/job.sh
248
# 1. dump the existing cron:
249
# crontab -l -u <user> > /tmp/tmpfile
250
# 2. search for comment "^# Ansible: <name>" followed by a cron.
251
# 3. if absent: remove if present (and say modified), otherwise return with no mod.
252
# 4. if present: if the same return no mod, if not present add (and say mod), if different add (and say mod)
253
# 5. Install new cron (if mod):
254
# crontab -u <user> /tmp/tmpfile
257
module = AnsibleModule(
258
argument_spec = dict(
259
name=dict(required=True),
260
user=dict(required=False),
261
job=dict(required=False),
262
cron_file=dict(required=False),
263
state=dict(default='present', choices=['present', 'absent']),
264
backup=dict(default=False, type='bool'),
265
minute=dict(default='*'),
266
hour=dict(default='*'),
267
day=dict(default='*'),
268
month=dict(default='*'),
269
weekday=dict(default='*'),
270
reboot=dict(required=False, default=False, type='bool')
274
backup = module.params['backup']
275
name = module.params['name']
276
user = module.params['user']
277
job = module.params['job']
278
cron_file = module.params['cron_file']
279
minute = module.params['minute']
280
hour = module.params['hour']
281
day = module.params['day']
282
month = module.params['month']
283
weekday = module.params['weekday']
284
reboot = module.params['reboot']
285
state = module.params['state']
286
do_install = module.params['state'] == 'present'
289
if reboot and (True in [(x != '*') for x in [minute, hour, day, month, weekday]]):
290
module.fail_json(msg="You must specify either reboot=True or any of minute, hour, day, month, weekday")
294
module.fail_json(msg="To use file=... parameter you must specify user=... as well")
299
user = "-u %s" % (user)
301
job = get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot)
302
rc, out, err, rm, status = (0, None, None, None, None)
303
if job is None and do_install:
304
module.fail_json(msg="You must specify 'job' to install a new cron job")
306
tmpfile = tempfile.NamedTemporaryFile()
307
(rc, out, err) = get_jobs_file(module,user,tmpfile.name, cron_file)
309
if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
310
module.fail_json(msg=err)
312
(handle,backupfile) = tempfile.mkstemp(prefix='crontab')
313
(rc, out, err) = get_jobs_file(module,user,backupfile, cron_file)
314
if rc != 0 and rc != 1:
315
module.fail_json(msg=err)
317
old_job = find_job(name,backupfile)
319
if len(old_job) == 0:
320
add_job(module,name,job,tmpfile.name)
322
if len(old_job) > 0 and old_job[1] != job:
323
update_job(name,job,tmpfile.name)
327
# if rm is true after the next line, file will be deleted afterwards
328
rm = remove_job(name,tmpfile.name)
331
# there is no old_jobs for deletion - we should leave everything
332
# as is. If the file is empty, it will be removed later
334
# the file created by mks should be deleted explicitly
335
os.unlink(backupfile)
336
module.exit_json(changed=changed,cron_file=cron_file,state=state)
339
# If the file is empty - remove it
341
remove_job_file(cron_file)
344
module.backup_local(backupfile)
345
(rc, out, err) = install_jobs(module,user,tmpfile.name, cron_file)
347
module.fail_json(msg=err)
349
# get the list of jobs in file
351
for j in get_jobs(tmpfile.name):
352
jobnames.append(j[0])
356
os.unlink(backupfile)
357
module.exit_json(changed=changed,jobs=jobnames)
359
module.exit_json(changed=changed,jobs=jobnames,backup=backupfile)
361
# include magic from lib/ansible/module_common.py
362
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>