Skip to main content

User Crontab install buildout recipe

Project description

The problem

When deploying applications, it can be useful to have maintenance tasks be started periodically. On Unix platforms this is usually done using cron which starts cronjobs. Adding cronjobs to the system-wide cron directory (for example by placing a file in /etc/cron.d) can be handled using the zc.recipe.deployment package, but it does not support adding cronjobs by normal users. (as /etc/cron.d usually is world-writable).

The solution

z3c.recipe.usercrontab interfaces with cron using crontab(1), and allows normal users to install their own cronjobs. This is done by having buildout add and remove cronjobs when installing and uninstalling packages.

How to use it

To use z3c.recipe.usercrontab you need to add the following to your buildout.cfg:

[mycronjob]
recipe = z3c.recipe.usercrontab
times = 0 12 * * *
command = echo nothing happens at noon

and finally add mycronjob to the parts line(s) of your buildout.cfg

Detailed documentation

# Copyright (c) 2009 Zope Foundation and contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED “AS IS” AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE.

The recipe z3c.recipe.usercrontab is a small recipe to facilitate the installing of cronjobs into user crontabs.

>>> from z3c.recipe.usercrontab.usercrontab import UserCrontabManager

Entry handling

A user crontab manager manages a user’s crontab for one specific buildout part. The part ends up in the identifier. We’ll use ‘test’ here.

>>> c = UserCrontabManager(identifier='test')

In these tests, we can fake a crontab by filling the list of cron entries manually:

>>> c.crontab = ['@reboot echo "hello world"']
>>> print c # Handy shortcut
@reboot echo "hello world"

Now, we’re adding an entry to it using the official way. The entry is surrounded by markers:

>>> c.add_entry('@reboot echo "I just got added"')
>>> print c
@reboot echo "hello world"
<BLANKLINE>
# Generated by test
@reboot echo "I just got added"
# END test
<BLANKLINE>

Removing entries also works. As long as the “Generated by” markers are present, it doesn’t matter which entry you remove: everything surrounded by the markers is zapped:

>>> c.del_entry('bla bla') == 1
True
>>> print c
@reboot echo "hello world"

Pre-0.6, a WARNING environment variable was used. An entry (which content matters now!) is found there:

>>> c.crontab = ['@reboot echo "hello world"',
...              'WARNING="Everything below is added by bla bla',
...              '@reboot echo "old entry 1"',
...              '@reboot echo "old entry 2"']
>>> print c
@reboot echo "hello world"
WARNING="Everything below is added by bla bla
@reboot echo "old entry 1"
@reboot echo "old entry 2"
>>> c.del_entry('@reboot echo "old entry 1"')
1
>>> print c
@reboot echo "hello world"
WARNING="Everything below is added by bla bla
@reboot echo "old entry 2"

Removing the last remaining entry under WARNING also removes the WARNING:

>>> c.del_entry('@reboot echo "old entry 2"')
1
>>> print c
@reboot echo "hello world"

Briefly in the 0.5 version, a ‘BUILDOUT’ environment variable was used for grouping items per buildout. Now for some up/downgrade testing. 0.5.1 removes the environment variable again. We’ll add an entry with such a (now deprecated) “grouping environment variable”. First the start situation:

>>> c.crontab=[
...     'WARNING="Everything below is added by bla bla',
...     'BUILDOUT=my/buildout',
...     '@reboot echo nothing happens']
>>> print c
WARNING="Everything below is added by bla bla
BUILDOUT=my/buildout
@reboot echo nothing happens

Doing anything (adding/removing) zaps BUILDOUT statement:

>>> c.del_entry('nonexisting')
0
>>> print c
WARNING="Everything below is added by bla bla
@reboot echo nothing happens

And just to make sure, deleting that entry empties out the whole file:

>>> c.del_entry('@reboot echo nothing happens')
1
>>> print c
<BLANKLINE>

Read/write crontab methods

Next, test the read_crontab and write_crontab methods; we’ll use cat and a temporary file to not modifiy the crontab of the user running these tests:

>>> import tempfile
>>> t = tempfile.NamedTemporaryFile('w')
>>> crontestfile = t.name
>>> t.write("#dummy\n")
>>> c = UserCrontabManager(readcrontab="cat %s" % crontestfile,
...                        writecrontab="cat >%s" % crontestfile,
...                        identifier='test')
>>> c.read_crontab()
>>> a = repr(c)
>>> c.add_entry('# improbable entry')
>>> c.write_crontab()
>>> c.read_crontab()
>>> b =repr(c)
>>> a == b
False

Now, delete this entry again and make sure the old crontab is restored:

>>> c.del_entry('# improbable entry') == 1
True
>>> c.write_crontab()
>>> c.read_crontab()
>>> b = repr(c)
>>> a == b
True

Buildout recipe usage

Do the buildout shuffle:

>>> write('buildout.cfg',
... '''
... [buildout]
... parts = foo
...
... [foo]
... recipe = z3c.recipe.usercrontab
... times = # @reboot
... command = echo nothing happens
... readcrontab = cat %(crontest)s
... writecrontab = cat >%(crontest)s
... ''' % ( { 'crontest': crontestfile } ))
>>> import os
>>> print 'start', system(buildout)
start...
Installing foo.
<BLANKLINE>

Check that it really was added to the crontab:

>>> c.read_crontab()
>>> b = repr(c)
>>> a == b
False
>>> '# @reboot\techo nothing happens' in c.crontab
True
>>> print c
<BLANKLINE>
# Generated by /sample-buildout [foo]
# @reboot   echo nothing happens
# END /sample-buildout [foo]
<BLANKLINE>

Uninstall the recipe:

>>> write('buildout.cfg',
... '''
... [buildout]
... parts =
... ''' % ( { 'crontest': crontestfile } ))
>>> print 'start', system(buildout)
start...
Uninstalling foo.
Running uninstall recipe.
<BLANKLINE>

And check that its entry was removed (i.e., the contents of the crontab are the same as when this test was started; in any case, the teardown from the testrunner makes sure the old situation is restored):

>>> c.read_crontab()
>>> b = repr(c)
>>> a == b
True

A second part installs fine:

>>> write('buildout.cfg',
... '''
... [buildout]
... parts = foo bar
...
... [foo]
... recipe = z3c.recipe.usercrontab
... times = # @reboot
... command = echo nothing happens
... readcrontab = cat %(crontest)s
... writecrontab = cat >%(crontest)s
...
... [bar]
... recipe = z3c.recipe.usercrontab
... times = # @reboot
... command = echo something happens
... readcrontab = cat %(crontest)s
... writecrontab = cat >%(crontest)s
... ''' % ( { 'crontest': crontestfile } ))
>>> print 'start', system(buildout)
start...
Installing foo.
Installing bar.
<BLANKLINE>
>>> c.read_crontab()
>>> print c
<BLANKLINE>
# Generated by /sample-buildout [foo]
# @reboot   echo nothing happens
# END /sample-buildout [foo]
<BLANKLINE>
<BLANKLINE>
# Generated by /sample-buildout [bar]
# @reboot   echo something happens
# END /sample-buildout [bar]
<BLANKLINE>

Uninstalling also works fine

>>> write('buildout.cfg',
... '''
... [buildout]
... parts =
... ''' % ( { 'crontest': crontestfile } ))
>>> print 'start', system(buildout)
start...
Uninstalling bar.
Running uninstall recipe.
Uninstalling foo.
Running uninstall recipe.
<BLANKLINE>

Safety valves

If the section has been removed, nothing can be found by the uninstall. You get warnings that way:

>>> write('buildout.cfg',
... '''
... [buildout]
... parts = foo
...
... [foo]
... recipe = z3c.recipe.usercrontab
... times = # @reboot
... command = echo nothing happens
... readcrontab = cat %(crontest)s
... writecrontab = cat >%(crontest)s
... ''' % ( { 'crontest': crontestfile } ))
>>> import os
>>> print 'start', system(buildout)
start...
Installing foo.
<BLANKLINE>
>>> c.crontab = []
>>> c.write_crontab()
>>> write('buildout.cfg',
... '''
... [buildout]
... parts =
... ''' % ( { 'crontest': crontestfile } ))
>>> print 'start', system(buildout)
start...
Uninstalling foo.
Running uninstall recipe.
foo: WARNING: Did not find a crontab-entry during uninstall; please check manually if everything was removed correctly
<BLANKLINE>

Another test: pre-0.6 config simulation:

>>> write('buildout.cfg',
... '''
... [buildout]
... parts = foo
...
... [foo]
... recipe = z3c.recipe.usercrontab
... times = # @reboot
... command = echo nothing happens
... readcrontab = cat %(crontest)s
... writecrontab = cat >%(crontest)s
... ''' % ( { 'crontest': crontestfile } ))
>>> import os
>>> print 'start', system(buildout)
start...
Installing foo.
<BLANKLINE>
>>> c.crontab = ['WARNING="Everything below is added by bla bla"',
...              'BUILDOUT=/somewhere/out/there',
...              '# @reboot\techo nothing happens']
>>> c.write_crontab()
>>> write('buildout.cfg',
... '''
... [buildout]
... parts =
... ''' % ( { 'crontest': crontestfile } ))
>>> print 'start', system(buildout)
start...
Uninstalling foo.
Running uninstall recipe.
<BLANKLINE>
>>> c.read_crontab()
>>> print c
<BLANKLINE>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

z3c.recipe.usercrontab-0.6.tar.gz (10.2 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page