[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