ansible/cron
Dag Wieers d4bb57d42d Fix the documentation booleans so they use "yes" and "no"
Both modules seboolean and zfs have not been adapted since they defer from the default (either by having a 'null' or special state, or prefering "on"/"off" for state indication.
2013-03-12 13:25:59 +01:00

364 lines
12 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2012, Dane Summers <dsummers@pinedesk.biz>
# (c) 2013, Mike Grozak <mike.grozak@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Cron Plugin: The goal of this plugin is to provide an indempotent method for
# setting up cron jobs on a host. The script will play well with other manually
# entered crons. Each cron job entered will be preceded with a comment
# describing the job so that it can be found later, which is required to be
# present in order for this plugin to find/modify the job.
DOCUMENTATION = """
---
module: cron
short_description: Manage crontab entries.
description:
- Use this module to manage crontab entries. This module allows you to create named
crontab entries, update, or delete them.
- 'The module includes one line with the description of the crontab entry C("#Ansible: <name>")
corresponding to the "name" passed to the module, which is used by future ansible/module calls
to find/check the state.'
version_added: "0.9"
options:
name:
description:
- Description of a crontab entry.
required: true
default:
aliases: []
user:
description:
- The specific user who's crontab should be modified.
required: false
default: root
aliases: []
job:
description:
- The command to execute.
- Required if state=present.
required: false
default:
aliases: []
state:
description:
- Whether to ensure the job is present or absent.
required: false
default: present
aliases: []
cron_file:
description:
- If specified, uses this file in cron.d versus in the main crontab
required: false
default:
aliases: []
backup:
description:
- If set, then create a backup of the crontab before it is modified.
- The location of the backup is returned in the C(backup) variable by this module.
required: false
default: false
aliases: []
minute:
description:
- Minute when the job should run ( 0-59, *, */2, etc )
required: false
default: "*"
aliases: []
hour:
description:
- Hour when the job should run ( 0-23, *, */2, etc )
required: false
default: "*"
aliases: []
day:
description:
- Day of the month the job should run ( 1-31, *, */2, etc )
required: false
default: "*"
aliases: []
month:
description:
- Month of the year the job should run ( 1-12, *, */2, etc )
required: false
default: "*"
aliases: []
weekday:
description:
- Day of the week that the job should run ( 0-7 for Sunday - Saturday, or mon, tue, * etc )
required: false
default: "*"
aliases: []
reboot:
description:
- If the job should be run at reboot, will ignore minute, hour, day, and month settings in favour of C(@reboot)
version_added: "1.0"
required: false
default: "no"
choices: [ "yes", "no" ]
aliases: []
examples:
- code: 'cron: name="check dirs" hour="5,2" job="ls -alh > /dev/null"'
description: Ensure a job that runs at 2 and 5 exists. Creates an entry like "* 5,2 * * ls -alh > /dev/null"
- code: 'cron: name="an old job" cron job="/some/dir/job.sh" state=absent'
description: 'Ensure an old job is no longer present. Removes any job that is preceded by "#Ansible: an old job" in the crontab'
- code: 'cron: name="a job for reboot" reboot=yes job="/some/job.sh"'
description: 'Creates an entry like "@reboot /some/job.sh"'
- 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'
requirements: cron
author: Dane Summers
updates: Mike Grozak
"""
import re
import tempfile
def get_jobs_file(module, user, tmpfile, cron_file):
if cron_file:
cmd = "cp -fp /etc/cron.d/%s %s" % (cron_file, tmpfile)
else:
cmd = "crontab -l %s > %s" % (user,tmpfile)
return module.run_command(cmd)
def install_jobs(module, user, tmpfile, cron_file):
if cron_file:
cmd = "ln -f %s /etc/cron.d/%s" % (tmpfile, cron_file)
else:
cmd = "crontab %s %s" % (user, tmpfile)
return module.run_command(cmd)
def get_jobs(tmpfile):
lines = open(tmpfile).read().splitlines()
comment = None
jobs = []
for l in lines:
if comment is not None:
jobs.append([comment,l])
comment = None
elif re.match( r'#Ansible: ',l):
comment = re.sub( r'#Ansible: ', '', l)
return jobs
def find_job(name,tmpfile):
jobs = get_jobs(tmpfile)
for j in jobs:
if j[0] == name:
return j
return []
def add_job(module,name,job,tmpfile):
f = open(tmpfile, 'a')
f.write("#Ansible: %s\n%s\n" % (name, job))
f.close()
def update_job(name,job,tmpfile):
return _update_job(name,job,tmpfile,do_add_job)
def do_add_job(lines, comment, job):
lines.append(comment)
lines.append(job)
def remove_job(name,tmpfile):
return _update_job(name, "", tmpfile, do_remove_job)
def do_remove_job(lines,comment,job):
return None
def remove_job_file(cron_file):
fname = "/etc/cron.d/%s" % (cron_file)
os.unlink(fname)
def _update_job(name,job,tmpfile,addlinesfunction):
ansiblename = "#Ansible: %s" % (name)
f = open(tmpfile)
lines = f.read().splitlines()
f.close()
newlines = []
comment = None
for l in lines:
if comment is not None:
addlinesfunction(newlines,comment,job)
comment = None
elif l == ansiblename:
comment = l
else:
newlines.append(l)
f = open(tmpfile, 'w')
for l in newlines:
f.write(l)
f.write('\n')
f.close()
if len(newlines) == 0:
return True
else:
return False # TODO add some more error testing
def get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot):
if reboot:
if cron_file:
return "@reboot %s %s" % (user, job)
else:
return "@reboot %s" % (job)
else:
if cron_file:
return "%s %s %s %s %s %s %s" % (minute,hour,day,month,weekday,user,job)
else:
return "%s %s %s %s %s %s" % (minute,hour,day,month,weekday,job)
return None
def main():
# The following example playbooks:
# - action: cron name="check dirs" hour="5,2" job="ls -alh > /dev/null"
# - name: do the job
# action: name="do the job" cron hour="5,2" job="/some/dir/job.sh"
# - name: no job
# action: name="an old job" cron job="/some/dir/job.sh" state=absent
#
# Would produce:
# # Ansible: check dirs
# * * 5,2 * * ls -alh > /dev/null
# # Ansible: do the job
# * * 5,2 * * /some/dir/job.sh
# Function:
# 1. dump the existing cron:
# crontab -l -u <user> > /tmp/tmpfile
# 2. search for comment "^# Ansible: <name>" followed by a cron.
# 3. if absent: remove if present (and say modified), otherwise return with no mod.
# 4. if present: if the same return no mod, if not present add (and say mod), if different add (and say mod)
# 5. Install new cron (if mod):
# crontab -u <user> /tmp/tmpfile
# 6. return mod
module = AnsibleModule(
argument_spec = dict(
name=dict(required=True),
user=dict(required=False),
job=dict(required=False),
cron_file=dict(required=False),
state=dict(default='present', choices=['present', 'absent']),
backup=dict(default=False, type='bool'),
minute=dict(default='*'),
hour=dict(default='*'),
day=dict(default='*'),
month=dict(default='*'),
weekday=dict(default='*'),
reboot=dict(required=False, default=False, type='bool')
)
)
backup = module.params['backup']
name = module.params['name']
user = module.params['user']
job = module.params['job']
cron_file = module.params['cron_file']
minute = module.params['minute']
hour = module.params['hour']
day = module.params['day']
month = module.params['month']
weekday = module.params['weekday']
reboot = module.params['reboot']
state = module.params['state']
do_install = module.params['state'] == 'present'
changed = False
if reboot and (True in [(x != '*') for x in [minute, hour, day, month, weekday]]):
module.fail_json(msg="You must specify either reboot=True or any of minute, hour, day, month, weekday")
if cron_file:
if not user:
module.fail_json(msg="To use file=... parameter you must specify user=... as well")
else:
if not user:
user = ""
else:
user = "-u %s" % (user)
job = get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot)
rc, out, err, rm, status = (0, None, None, None, None)
if job is None and do_install:
module.fail_json(msg="You must specify 'job' to install a new cron job")
tmpfile = tempfile.NamedTemporaryFile()
(rc, out, err) = get_jobs_file(module,user,tmpfile.name, cron_file)
if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
module.fail_json(msg=err)
(handle,backupfile) = tempfile.mkstemp(prefix='crontab')
(rc, out, err) = get_jobs_file(module,user,backupfile, cron_file)
if rc != 0 and rc != 1:
module.fail_json(msg=err)
old_job = find_job(name,backupfile)
if do_install:
if len(old_job) == 0:
add_job(module,name,job,tmpfile.name)
changed = True
if len(old_job) > 0 and old_job[1] != job:
update_job(name,job,tmpfile.name)
changed = True
else:
if len(old_job) > 0:
# if rm is true after the next line, file will be deleted afterwards
rm = remove_job(name,tmpfile.name)
changed = True
else:
# there is no old_jobs for deletion - we should leave everything
# as is. If the file is empty, it will be removed later
tmpfile.close()
# the file created by mks should be deleted explicitly
os.unlink(backupfile)
module.exit_json(changed=changed,cron_file=cron_file,state=state)
if changed:
# If the file is empty - remove it
if rm and cron_file:
remove_job_file(cron_file)
else:
if backup:
module.backup_local(backupfile)
(rc, out, err) = install_jobs(module,user,tmpfile.name, cron_file)
if (rc != 0):
module.fail_json(msg=err)
# get the list of jobs in file
jobnames = []
for j in get_jobs(tmpfile.name):
jobnames.append(j[0])
tmpfile.close()
if not backup:
os.unlink(backupfile)
module.exit_json(changed=changed,jobs=jobnames)
else:
module.exit_json(changed=changed,jobs=jobnames,backup=backupfile)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()