Metadata-Version: 1.0
Name: z3c.recipe.usercrontab
Version: 0.6
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
        ======================
        .. -*- mode: doctest -*-
        
        # 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>
        
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
