[Checkins] SVN: zc.buildout/trunk/ Added uninstall recipes feature.
Amos Latteier
amos at latteier.com
Tue Dec 5 15:53:38 EST 2006
Log message for revision 71422:
Added uninstall recipes feature.
Changed:
U zc.buildout/trunk/CHANGES.txt
U zc.buildout/trunk/src/zc/buildout/buildout.py
U zc.buildout/trunk/src/zc/buildout/buildout.txt
-=-
Modified: zc.buildout/trunk/CHANGES.txt
===================================================================
--- zc.buildout/trunk/CHANGES.txt 2006-12-05 20:48:08 UTC (rev 71421)
+++ zc.buildout/trunk/CHANGES.txt 2006-12-05 20:53:37 UTC (rev 71422)
@@ -20,6 +20,15 @@
Change History
**************
+Unreleased version
+==================
+
+Feature Changes
+---------------
+
+- Added uninstall recipes for dealing with complex uninstallation
+ scenarios.
+
1.0.0b13 (2006-12-04)
=====================
Modified: zc.buildout/trunk/src/zc/buildout/buildout.py
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.py 2006-12-05 20:48:08 UTC (rev 71421)
+++ zc.buildout/trunk/src/zc/buildout/buildout.py 2006-12-05 20:53:37 UTC (rev 71422)
@@ -248,6 +248,20 @@
# ununstall part
self._logger.info('Uninstalling %s', part)
+
+ # run uinstall recipe
+ recipe = installed_part_options[part].get('uninstall')
+ if recipe:
+ if ':' in recipe:
+ recipe, entry = recipe.split(':')
+ else:
+ entry = 'default'
+ self._logger.info('Running uninstall recipe')
+ uninstaller = pkg_resources.load_entry_point(
+ recipe, 'zc.buildout.uninstall', entry)
+ uninstaller(part, installed_part_options[part])
+
+ # remove created files and directories
self._uninstall(
installed_part_options[part]['__buildout_installed__'])
installed_parts = [p for p in installed_parts if p != part]
Modified: zc.buildout/trunk/src/zc/buildout/buildout.txt
===================================================================
--- zc.buildout/trunk/src/zc/buildout/buildout.txt 2006-12-05 20:48:08 UTC (rev 71421)
+++ zc.buildout/trunk/src/zc/buildout/buildout.txt 2006-12-05 20:53:37 UTC (rev 71422)
@@ -197,7 +197,8 @@
returned will be removed by the buildout machinery. A recipe install
method is expected to return a string, or an iterable of strings
containing paths to be removed if a part is uninstalled. For most
-recipes, this is all of the uninstall support needed.
+recipes, this is all of the uninstall support needed. For more complex
+uninstallation scenarios use `Uninstall recipes`_.
The update method is responsible for updating an already installed
part. An empty method is often provided, as in this example, if parts
@@ -795,6 +796,238 @@
op3 b2 3
recipe recipes:debug
+Uninstall recipes
+-----------------
+
+As we've seen, when parts are installed, buildout keeps track of files
+and directories that they create. When the parts are uninstalled these
+files and directories are deleted.
+
+Sometimes more clean up is needed. For example, a recipe might add a
+system service by calling chkconfig --add during installation. Later
+during uninstallation, chkconfig --del will need to be called to
+remove the system service.
+
+In order to deal with these uninstallation issues, you can register
+uninstall recipes. Uninstall recipes are registered using the
+'zc.buildout.uninstall' entry point. Parts specify uninstall recipes
+using the 'uninstall' option.
+
+In comparison to regular recipes, uninstall recipes are much
+simpler. They are simply callable objects that accept the name of the
+part to be uninstalled and the part's options dictionary. Uninstall
+recipes don't have access to the part itself since it maybe not be
+able to be instantiated at uninstallation time.
+
+Here's a recipe that simulates installation of a system service, along
+with an uninstall recipe that simulates removing the service.
+
+ >>> write(sample_buildout, 'recipes', 'service.py',
+ ... """
+ ... class Service:
+ ...
+ ... def __init__(self, buildout, name, options):
+ ... self.buildout = buildout
+ ... self.name = name
+ ... self.options = options
+ ...
+ ... def install(self):
+ ... print "chkconfig --add %s" % self.options['script']
+ ... return ()
+ ...
+ ... def update(self):
+ ... pass
+ ...
+ ...
+ ... def uninstall_service(name, options):
+ ... print "chkconfig --del %s" % options['script']
+ ... """)
+
+To use these recipes we must register them using entry points.
+
+ >>> write(sample_buildout, 'recipes', 'setup.py',
+ ... """
+ ... from setuptools import setup
+ ... entry_points = (
+ ... '''
+ ... [zc.buildout]
+ ... mkdir = mkdir:Mkdir
+ ... debug = debug:Debug
+ ... service = service:Service
+ ...
+ ... [zc.buildout.uninstall]
+ ... uninstall_service = service:uninstall_service
+ ... ''')
+ ... setup(name="recipes", entry_points=entry_points)
+ ... """)
+
+Here's how these recipes could be used in a buildout:
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... develop = recipes
+ ... parts = service
+ ...
+ ... [service]
+ ... recipe = recipes:service
+ ... script = /path/to/script
+ ... uninstall = recipes:uninstall_service
+ ... """)
+
+When the buildout is run the service will be installed
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Uninstalling debug
+ buildout: Installing service
+ chkconfig --add /path/to/script
+ <BLANKLINE>
+
+The service has been installed. If the buildout is run again with no changes, the serivce shouldn't be changed.
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Updating service
+ <BLANKLINE>
+
+Now we change the service part to trigger uninstallation and
+re-installation.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... develop = recipes
+ ... parts = service
+ ...
+ ... [service]
+ ... recipe = recipes:service
+ ... script = /path/to/a/different/script
+ ... uninstall = recipes:uninstall_service
+ ... """)
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Uninstalling service
+ buildout: Running uninstall recipe
+ chkconfig --del /path/to/script
+ buildout: Installing service
+ chkconfig --add /path/to/a/different/script
+ <BLANKLINE>
+
+Now we remove the service part, and add another part.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... develop = recipes
+ ... parts = debug
+ ...
+ ... [debug]
+ ... recipe = recipes:debug
+ ... """)
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Uninstalling service
+ buildout: Running uninstall recipe
+ chkconfig --del /path/to/a/different/script
+ buildout: Installing debug
+ recipe recipes:debug
+ <BLANKLINE>
+
+Uninstall recipes don't have to take care of removing all the files
+and directories created by the part. This is still done automatically,
+following the execution of the uninstall recipe. An upshot is that an
+uninstallation recipe can access files and directories created by a
+recipe before they are deleted.
+
+For example, here's an uninstallation recipe that simulates backing up
+a directory.
+
+ >>> write(sample_buildout, 'recipes', 'backup.py',
+ ... """
+ ... import os
+ ... def backup_directory(name, options):
+ ... path = options['path']
+ ... size = os.stat(path).st_size
+ ... print "backing up directory %s of size %s" % (path, size)
+ ... """)
+
+It must be registered with the zc.buildout.uninstall entry point.
+
+ >>> write(sample_buildout, 'recipes', 'setup.py',
+ ... """
+ ... from setuptools import setup
+ ... entry_points = (
+ ... '''
+ ... [zc.buildout]
+ ... mkdir = mkdir:Mkdir
+ ... debug = debug:Debug
+ ... service = service:Service
+ ...
+ ... [zc.buildout.uninstall]
+ ... uninstall_service = service:uninstall_service
+ ... backup = backup:backup_directory
+ ... ''')
+ ... setup(name="recipes", entry_points=entry_points)
+ ... """)
+
+Now we can use it with a part. It's necessary to pick a part that
+defines a 'path' option, since that's what the uninstall recipe
+expects.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... develop = recipes
+ ... parts = dir debug
+ ...
+ ... [dir]
+ ... recipe = recipes:mkdir
+ ... uninstall = recipes:backup
+ ... path = my_directory
+ ...
+ ... [debug]
+ ... recipe = recipes:debug
+ ... """)
+
+Run the buildout to install the part.
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Uninstalling debug
+ buildout: Installing dir
+ dir: Creating directory my_directory
+ buildout: Installing debug
+ recipe recipes:debug
+ <BLANKLINE>
+
+Now we remove the part from the configuration file.
+
+ >>> write(sample_buildout, 'buildout.cfg',
+ ... """
+ ... [buildout]
+ ... develop = recipes
+ ... parts = debug
+ ...
+ ... [debug]
+ ... recipe = recipes:debug
+ ... """)
+
+When the buildout is run the part is removed, and the uninstall recipe
+is run before the directory is deleted.
+
+ >>> print system(buildout)
+ buildout: Develop: /sample-buildout/recipes
+ buildout: Uninstalling dir
+ buildout: Running uninstall recipe
+ backing up directory /sample-buildout/my_directory of size 4096
+ buildout: Updating debug
+ recipe recipes:debug
+ <BLANKLINE>
+
+
Command-line usage
------------------
@@ -895,8 +1128,7 @@
>>> print system(buildout),
buildout: Develop: /sample-buildout/recipes
- buildout: Uninstalling debug
- buildout: Installing debug
+ buildout: Updating debug
recipe recipes:debug
buildout: Installing d1
d1: Creating directory d1
More information about the Checkins
mailing list