[Checkins] SVN: zc.recipe.deployment/trunk/ New recipe to update configuration files that may be shared by multiple applications.

Satchidanand Haridas satchit at zope.com
Tue May 18 10:29:10 EDT 2010


Log message for revision 112454:
  New recipe to update configuration files that may be shared by multiple applications.
  
  

Changed:
  U   zc.recipe.deployment/trunk/README.txt
  U   zc.recipe.deployment/trunk/setup.py
  U   zc.recipe.deployment/trunk/src/zc/recipe/deployment/README.txt
  U   zc.recipe.deployment/trunk/src/zc/recipe/deployment/__init__.py

-=-
Modified: zc.recipe.deployment/trunk/README.txt
===================================================================
--- zc.recipe.deployment/trunk/README.txt	2010-05-18 14:26:26 UTC (rev 112453)
+++ zc.recipe.deployment/trunk/README.txt	2010-05-18 14:29:10 UTC (rev 112454)
@@ -18,7 +18,7 @@
 etc-directory
     The name of the directory where configuration files should be
     placed.  This is /etc/NAME, where NAME is the deployment
-    name. 
+    name.
 
 log-directory
     The name of the directory where application instances should write
@@ -39,13 +39,22 @@
     The name of the directory where run-control scripts should be
     installed.  This is /etc/init.d.
 
-The etc, log, and run directories are created in such a way that the 
+The etc, log, and run directories are created in such a way that the
 directories are owned by the user specified in the user option and are
 writable by the user and the user's group.
 
 Changes
 *******
 
+0.8.0 (2010-05-18)
+==================
+
+Features Added
+--------------
+
+Added recipe for updating configuration files that may shared by
+multiple applications.
+
 0.7.1 (2010-03-05)
 ==================
 

Modified: zc.recipe.deployment/trunk/setup.py
===================================================================
--- zc.recipe.deployment/trunk/setup.py	2010-05-18 14:26:26 UTC (rev 112453)
+++ zc.recipe.deployment/trunk/setup.py	2010-05-18 14:29:10 UTC (rev 112454)
@@ -9,9 +9,11 @@
 deployment = %(name)s:Install
 configuration = %(name)s:Configuration
 crontab = %(name)s:Crontab
+sharedconfig = %(name)s:SharedConfig
 
 [zc.buildout.uninstall]
 default = %(name)s:uninstall
+sharedconfig = %(name)s:uninstall_shared_config
 
 ''' % globals()
 
@@ -20,7 +22,7 @@
 
 setup(
     name = name,
-    version = '0.7dev',
+    version = '0.8dev',
     author = 'Jim Fulton',
     author_email = 'jim at zope.com',
     description = 'ZC Buildout recipe for Unix deployments',

Modified: zc.recipe.deployment/trunk/src/zc/recipe/deployment/README.txt
===================================================================
--- zc.recipe.deployment/trunk/src/zc/recipe/deployment/README.txt	2010-05-18 14:26:26 UTC (rev 112453)
+++ zc.recipe.deployment/trunk/src/zc/recipe/deployment/README.txt	2010-05-18 14:29:10 UTC (rev 112454)
@@ -564,3 +564,357 @@
 
     >>> os.path.exists(os.path.join(sample_buildout, 'etc/cron.d/bar-cron'))
     False
+
+
+SharedConfig
+============
+
+This recipe can be used to update configuration files that are shared by
+multiple applications. The absolute path of the file must be specified. Also,
+the configuration files must accept comments that start with "#".
+
+Like the configuration recipe, the content to add in the configuration file can
+be provided using the "text" or the "file" option.
+
+First let's create a file that will be used as the shared configuration file.
+
+    >>> open('y.cfg', 'w').write(
+    ... '''Some
+    ... existing
+    ... configuration
+    ... ''')
+
+We now create our buildout configuration and use the "sharedconfig" recipe and
+run buildout.
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo y.cfg
+    ...
+    ... [foo]
+    ... recipe = zc.recipe.deployment
+    ... prefix = %s
+    ... user = %s
+    ... etc-user = %s
+    ...
+    ... [y.cfg]
+    ... recipe = zc.recipe.deployment:sharedconfig
+    ... path = y.cfg
+    ... deployment = foo
+    ... text = xxx
+    ...        yyy
+    ...        zzz
+    ... ''' % (sample_buildout, user, user))
+
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Installing foo.
+    zc.recipe.deployment:
+        Creating 'PREFIX/etc/foo',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/var/log/foo',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/var/run/foo',
+        mode 750, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Updating 'PREFIX/etc/cron.d',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Updating 'PREFIX/etc/init.d',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Updating 'PREFIX/etc/logrotate.d',
+        mode 755, user 'USER', group 'GROUP'
+    Installing y.cfg.
+
+    >>> print open('y.cfg', 'r').read()
+    Some
+    existing
+    configuration
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    xxx
+    yyy
+    zzz
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+Running buildout again without modifying the configuration leaves the file the
+same.
+
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Updating foo.
+    Updating y.cfg.
+
+    >>> print open('y.cfg', 'r').read()
+    Some
+    existing
+    configuration
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    xxx
+    yyy
+    zzz
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+If we add some more lines to the file
+
+    >>> open('y.cfg', 'a').write(
+    ... '''Some
+    ... additional
+    ... configuration
+    ... ''')
+
+and run buildout again, but this time after modifying the configuration for
+"y.cfg", the sections will be moved to the end of the file.
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo y.cfg
+    ...
+    ... [foo]
+    ... recipe = zc.recipe.deployment
+    ... prefix = %s
+    ... user = %s
+    ... etc-user = %s
+    ...
+    ... [y.cfg]
+    ... recipe = zc.recipe.deployment:sharedconfig
+    ... path = y.cfg
+    ... deployment = foo
+    ... text = 111
+    ...        222
+    ...        333
+    ... ''' % (sample_buildout, user, user))
+
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Uninstalling y.cfg.
+    Running uninstall recipe.
+    Updating foo.
+    Installing y.cfg.
+
+    >>> print open('y.cfg', 'r').read()
+    Some
+    existing
+    configuration
+    Some
+    additional
+    configuration
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    111
+    222
+    333
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+The text to append to the shared configuration file can also be provided via a
+file.
+
+    >>> write('x.cfg', '''
+    ... [foo]
+    ... a = 1
+    ... b = 2
+    ...
+    ... [log]
+    ... c = 1
+    ... ''')
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo y.cfg
+    ...
+    ... [foo]
+    ... recipe = zc.recipe.deployment
+    ... prefix = %s
+    ... user = %s
+    ... etc-user = %s
+    ...
+    ... [y.cfg]
+    ... recipe = zc.recipe.deployment:sharedconfig
+    ... path = %s/etc/z.cfg
+    ... deployment = foo
+    ... file = x.cfg
+    ... ''' % (sample_buildout, user, user, sample_buildout))
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    While:
+      Installing.
+      Getting section y.cfg.
+      Initializing part y.cfg.
+    Error: Path 'PREFIX/etc/z.cfg' does not exist
+
+Oops. The path of the configuration file must exist. Let's create one.
+
+    >>> write(join(sample_buildout, 'etc', 'z.cfg'), '')
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Uninstalling y.cfg.
+    Running uninstall recipe.
+    Updating foo.
+    Installing y.cfg.
+
+    >>> print open(join(sample_buildout, 'etc', 'z.cfg'), 'r').read()
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    <BLANKLINE>
+    [foo]
+    a = 1
+    b = 2
+    <BLANKLINE>
+    [log]
+    c = 1
+    <BLANKLINE>
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+While uninstalling, only the lines that the recipe installed are removed.
+
+    >>> print system(join('bin', 'buildout')+' buildout:parts='),
+    Uninstalling y.cfg.
+    Running uninstall recipe.
+    Uninstalling foo.
+    Running uninstall recipe.
+    zc.recipe.deployment: Removing 'PREFIX/etc/foo'
+    zc.recipe.deployment: Removing 'PREFIX/etc/cron.d'.
+    zc.recipe.deployment: Removing 'PREFIX/etc/init.d'.
+    zc.recipe.deployment: Removing 'PREFIX/etc/logrotate.d'.
+    zc.recipe.deployment: Removing 'PREFIX/var/log/foo'.
+    zc.recipe.deployment: Removing 'PREFIX/var/run/foo'.
+
+But the files are not deleted.
+
+    >>> os.path.exists('y.cfg')
+    True
+
+    >>> print open('y.cfg', 'r').read()
+    Some
+    existing
+    configuration
+    Some
+    additional
+    configuration
+    <BLANKLINE>
+
+    >>> os.path.exists(join(sample_buildout, 'etc', 'z.cfg'))
+    True
+
+    >>> print open(join(sample_buildout, 'etc', 'z.cfg'), 'r').read()
+    <BLANKLINE>
+
+
+Edgecases
+---------
+
+The SharedConfig recipe checks to see if the current data in the file ends with
+a new line. If it doesn't exist it adds one. This is in addition to the blank
+line the recipe adds before the section to enhance readability.
+
+    >>> open('anotherconfig.cfg', 'w').write('one')
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo y.cfg
+    ...
+    ... [foo]
+    ... recipe = zc.recipe.deployment
+    ... prefix = %s
+    ... user = %s
+    ... etc-user = %s
+    ...
+    ... [y.cfg]
+    ... recipe = zc.recipe.deployment:sharedconfig
+    ... path = anotherconfig.cfg
+    ... deployment = foo
+    ... text = I predict that there will be a blank line above this.
+    ... ''' % (sample_buildout, user, user))
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Installing foo.
+    zc.recipe.deployment:
+        Creating 'PREFIX/etc/foo',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/var/log/foo',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/var/run/foo',
+        mode 750, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/etc/cron.d',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/etc/init.d',
+        mode 755, user 'USER', group 'GROUP'
+    zc.recipe.deployment:
+        Creating 'PREFIX/etc/logrotate.d',
+        mode 755, user 'USER', group 'GROUP'
+    Installing y.cfg.
+
+    >>> print open('anotherconfig.cfg').read()
+    one
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    I predict that there will be a blank line above this.
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+But the recipe doesn't add a new line if there was one already at the end.
+
+    >>> open('anotherconfig.cfg', 'w').write('ends with a new line\n')
+    >>> print open('anotherconfig.cfg').read()
+    ends with a new line
+    <BLANKLINE>
+
+We modify the buildout configuration so that "install" is invoked again:
+
+    >>> write('buildout.cfg',
+    ... '''
+    ... [buildout]
+    ... parts = foo y.cfg
+    ...
+    ... [foo]
+    ... recipe = zc.recipe.deployment
+    ... prefix = %s
+    ... user = %s
+    ... etc-user = %s
+    ...
+    ... [y.cfg]
+    ... recipe = zc.recipe.deployment:sharedconfig
+    ... path = anotherconfig.cfg
+    ... deployment = foo
+    ... text = there will still be only a single blank line above.
+    ... ''' % (sample_buildout, user, user))
+    >>> print system(join('bin', 'buildout')), # doctest: +NORMALIZE_WHITESPACE
+    Uninstalling y.cfg.
+    Running uninstall recipe.
+    Updating foo.
+    Installing y.cfg.
+
+    >>> print open('anotherconfig.cfg').read()
+    ends with a new line
+    <BLANKLINE>
+    #[foo_y.cfg DO NOT MODIFY LINES FROM HERE#
+    there will still be only a single blank line above.
+    #TILL HERE foo_y.cfg]#
+    <BLANKLINE>
+
+If we uninstall the file, the data will be the same as "original_data":
+
+    >>> print system(join('bin', 'buildout')+' buildout:parts='),
+    Uninstalling y.cfg.
+    Running uninstall recipe.
+    Uninstalling foo.
+    Running uninstall recipe.
+    zc.recipe.deployment: Removing 'PREFIX/etc/foo'
+    zc.recipe.deployment: Removing 'PREFIX/etc/cron.d'.
+    zc.recipe.deployment: Removing 'PREFIX/etc/init.d'.
+    zc.recipe.deployment: Removing 'PREFIX/etc/logrotate.d'.
+    zc.recipe.deployment: Removing 'PREFIX/var/log/foo'.
+    zc.recipe.deployment: Removing 'PREFIX/var/run/foo'.
+
+    >>> print open('anotherconfig.cfg').read()
+    ends with a new line
+    <BLANKLINE>

Modified: zc.recipe.deployment/trunk/src/zc/recipe/deployment/__init__.py
===================================================================
--- zc.recipe.deployment/trunk/src/zc/recipe/deployment/__init__.py	2010-05-18 14:26:26 UTC (rev 112453)
+++ zc.recipe.deployment/trunk/src/zc/recipe/deployment/__init__.py	2010-05-18 14:29:10 UTC (rev 112454)
@@ -162,3 +162,67 @@
 
     update = install
 
+
+begin_marker = '#[%s DO NOT MODIFY LINES FROM HERE#'
+end_marker = '#TILL HERE %s]#'
+
+class SharedConfig:
+
+    def __init__(self, buildout, name, options):
+        self.options = options
+        deployment = options.get('deployment')
+        options['entry_name'] = '%s_%s' % (buildout[deployment]['name'], name)
+        if not os.path.exists(options['path']):
+            raise zc.buildout.UserError(
+                "Path '%s' does not exist" % options['path'])
+        options['location'] = options['path']
+
+    def install(self):
+        options = self.options
+        if 'file' in options:
+            if 'text' in options:
+                raise zc.buildout.UserError(
+                    "Cannot specify both file and text options")
+            text = open(options['file'], 'r').read()
+        else:
+            text = options['text']
+        config_file = open(options['location'], 'r+')
+        current_data = config_file.read()
+        new_data = ''
+        if current_data and current_data[-1] != '\n':
+            new_data += '\n'
+        new_data += self._wrap_with_comments(options['entry_name'], text)
+        config_file.write(new_data)
+        config_file.close()
+        return ()
+
+    def _wrap_with_comments(self, entry_name, text):
+        return '\n%s\n%s\n%s\n' % (
+            begin_marker % entry_name, text, end_marker % entry_name)
+
+    def update(self):
+        pass
+
+
+def uninstall_shared_config(name, options):
+    old_config = open(options['location'], 'r').readlines()
+    new_config = []
+    block_start = False
+    for line in old_config:
+        if line.startswith('#[%s' % options['entry_name']):
+            # remove the newline we have added
+            if new_config[-1] == '\n':
+                new_config = new_config[:-1]
+            block_start = True
+            continue
+        elif line.strip().endswith('%s]#' % options['entry_name']):
+            block_start = False
+            continue
+        else:
+            if block_start:
+                continue
+            else:
+                new_config.append(line)
+
+    open(options['location'], 'w').write(''.join(new_config))
+



More information about the checkins mailing list