Metadata-Version: 1.0
Name: z3c.recipe.usercrontab
Version: 0.3
Summary: User Crontab install buildout recipe
Home-page: UNKNOWN
Author: Jasper Spaans, Jan-Jaap Driessen
Author-email: jspaans@thehealthagency.com
License: ZPL
Description: ======================
        z3c.recipe.usercrontab
        ======================
        
        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
        >>> c = UserCrontabManager()
        
        For these tests, we fake a crontab by filling the list of cron entries
        for this object::
        
        >>> c.crontab = [ 'MAILTO=""', '@reboot echo "No-one will see this"']
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        
        Now, we're adding a method to it using the official way::
        
        >>> c.add_entry('@reboot echo "example.com gets spammed!"',
        ...             MAILTO="example@example.com")
        
        The object also has a convenient __repr__, so we can test its output::
        
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        MAILTO=example@example.com
        @reboot echo "example.com gets spammed!"
        
        Adding another entry with yet another MAILTO line is placed at the end::
        
        >>> c.add_entry('@reboot echo "example.com gets spammed twice!"',
        ...              MAILTO="twice@example.com")
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        MAILTO=example@example.com
        @reboot echo "example.com gets spammed!"
        MAILTO=twice@example.com
        @reboot echo "example.com gets spammed twice!"
        
        When another entry is made with the same MAILTO, the MAILTO clause is
        not repeated again::
        
        >>> c.add_entry('@reboot echo "twice@example.com gets spammed twice!"',
        ...             MAILTO="twice@example.com")
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        MAILTO=example@example.com
        @reboot echo "example.com gets spammed!"
        MAILTO=twice@example.com
        @reboot echo "twice@example.com gets spammed twice!"
        @reboot echo "example.com gets spammed twice!"
        
        Removing entries also works, and removes superfluous environment variables::
        
        >>> c.del_entry('@reboot echo "example.com gets spammed!"') == 1
        True
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        MAILTO=twice@example.com
        @reboot echo "twice@example.com gets spammed twice!"
        @reboot echo "example.com gets spammed twice!"
        
        Removing entries does not remove too much::
        
        >>> c.del_entry('@reboot echo "twice@example.com gets spammed twice!"') == 1
        True
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        MAILTO=twice@example.com
        @reboot echo "example.com gets spammed twice!"
        
        Removing the last entry also removes the dangling MAILTO line::
        
        >>> c.del_entry('@reboot echo "example.com gets spammed twice!"') == 1
        True
        >>> print c
        MAILTO=""
        @reboot echo "No-one will see this"
        
        Removing the final entry removes the remaining MAILTO line, leaving us
        with an empty list::
        
        >>> c.del_entry('@reboot echo "No-one will see this"') == 1
        True
        >>> len(c.crontab)
        0
        
        Adding an entry without a MAILTO environment line also doesn't put in
        an empty one::
        
        >>> c.add_entry('@reboot echo "Someone will see this"')
        >>> print c
        @reboot echo "Someone will see this"
        
        Adding an entry with an empty MAILTO line adds it at the end, so the
        first entry is not disturbed::
        
        >>> c.add_entry('@reboot echo "No-one will see this"', MAILTO="")
        >>> print c
        @reboot echo "Someone will see this"
        MAILTO=""
        @reboot echo "No-one will see this"
        
        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)
        >>> 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
        
        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
        ...
        ... [bar]
        ... recipe = z3c.recipe.usercrontab
        ... times = # @reboot
        ... command = echo nothing happens
        ... readcrontab = cat %(crontest)s
        ... writecrontab = cat >%(crontest)s
        ... ''' % ( { 'crontest': crontestfile } ))
        
        
        >>> import os
        >>> print system(os.path.join('bin', 'buildout'))
        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
        
        >>> 'WARNING=The entries below were generated by buildout, do not modify' in c.crontab
        True
        
        Uninstall the recipe::
        
        >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=')
        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
        
        Now, break it by adding the same crontab entry twice::
        
        >>> print system(os.path.join('bin', 'buildout')+' "buildout:parts=foo bar"')
        Installing foo.
        Installing bar.
        <BLANKLINE>
        
        >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=') # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
        Uninstalling bar.
        Running uninstall recipe.
        bar: FATAL ERROR: Found more than one matching crontab-entry during uninstall; please resolve manually.
        Matched lines: # @reboot echo nothing happens
        While:
        Installing.
        Uninstalling bar.
        <BLANKLINE>
        An internal error occured due to a bug in either zc.buildout or in a
        recipe being used:
        Traceback (most recent call last):
        ...
        RuntimeError: Found more than one matching crontab-entry during uninstall
        <BLANKLINE>
        
        Manually fix it by removing the offending lines::
        >>> c.read_crontab()
        >>> c.del_entry("# @reboot\techo nothing happens")
        2
        >>> c.write_crontab()
        
        And now we can uninstall again (albeit with some warnings)::
        >>> print system(os.path.join('bin', 'buildout')+' buildout:parts=') # doctest:
        Uninstalling bar.
        Running uninstall recipe.
        bar: WARNING: Did not find a crontab-entry during uninstall; please check manually if everything was removed correctly
        Uninstalling foo.
        Running uninstall recipe.
        foo: WARNING: Did not find a crontab-entry during uninstall; please check manually if everything was removed correctly
        <BLANKLINE>
        
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Buildout
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: Zope Public License
