diff --git a/library/cron b/library/cron new file mode 100755 index 0000000000..514afbefa3 --- /dev/null +++ b/library/cron @@ -0,0 +1,370 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2012, Dane Summers +# (c) 2013, Mike Grozak +# +# 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 . + +# 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: ") + 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 +import shutil +import time +import os + +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: + dest_tmp = "%s.%s.%s.tmp" % (dest,os.getpid(),time.time()) + shutil.copyfile(tmpfile, dest_tmp) + module.atomic_replace(dest_tmp, dest) + 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 > /tmp/tmpfile + # 2. search for comment "^# Ansible: " 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 /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 +#<> + +main() + diff --git a/tasks/fas_client.yml b/tasks/fas_client.yml index 1cd53e13ae..7b6fa52156 100644 --- a/tasks/fas_client.yml +++ b/tasks/fas_client.yml @@ -49,12 +49,12 @@ # setup /etc/cron.d/ file to run sync every 10min # TODO: use cron module when it's fixed # -#- name: fas_client cron job -# cron: name="fas client" user=root cron_file=fas-client minute="*/10" job="/usr/bin/fasClient -i" -# tags: -# - config - - name: fas_client cron job - action: copy src=$files/fas-client/fas-client.cron dest=/etc/cron.d/fas-client owner=root mode=700 + cron: name="fas client" user=root cron_file=fas-client minute="*/10" job="/usr/bin/fasClient -i" tags: - config + +#- name: fas_client cron job +# action: copy src=$files/fas-client/fas-client.cron dest=/etc/cron.d/fas-client owner=root mode=700 +# tags: +# - config