GenericSetup-based product installation and migrations in Plone
An overview of how GenericSetup might be used to replace CMFQuickInstaller and the existing Plone site migration infrastructure.
For anyone who's been developing w/ Plone for a while, it's hardly news that the QuickInstaller and the Plone migration engine leave a bit to be desired. This article discusses a way that GenericSetup might be used to provide the functionality of both of these systems in a more elegant fashion.
First it is important to understand some fundamental GenericSetup concepts. GenericSetup introduces the idea of "setup profiles", which are a collection of XML files that describe aspects of site configuration. To get an idea what a setup profile looks like, you can browse through Plone's default profile from the subversion trunk. One thing to notice immediately about setup profiles is that they're declarative, not procedural; they describe the state that the site should end up in, not the steps taken to get there. Contrast this with the existing situation where site configuration can only be specified by a set of procedural python statements that actually perform the configuration. With GenericSetup, the procedural code is encapsulated into import / export handlers that know how to interpret the XML files of the profile and generate a configured site, and vice versa.
The setup profile that defines an initial site configuration is known as a "base profile". In addition to base profiles, GenericSetup supports "extension profiles", which (as you might guess) extend the base configuration with additional configuration options. Extension profiles usually specify such things as additional skin paths, additional content types, additional actions, etc. In short, pretty much all of the things that are usually handled by the Install.py module that QuickInstaller expects to find.
This then hints at the first part of the puzzle. Rather than creating an install method to be called by QuickInstaller, add-on products would define an extension profile that could be applied to the site. Installing the product would merely mean applying the extension profile to the site. Installing is the easy part, however; uninstall and reinstall support is quite a bit trickier to get right. Luckily, GenericSetup provides some mechanisms that can help with this. First, there are snapshots. A snapshot is simply a site export that is generated at a specific point in time and saved in the ZODB. Snapshots can also be compared to one another, providing an overview of what site configuration has changed in the time between when two snapshots were taken. If a snapshot is taken before an extension profile is applied, and then again afterward, it may be possible to determine what configuration changes actually occurred. Also, later snapshots can show what changes were made from when the profile was applied and some later time. Getting a perfectly clean automated uninstall will be tricky, but at the very least it should be possible to get a good idea about what needs to happen, and to even get warnings about later customizations that would potentially be impacted by the removal of the extension profile.
Another idea that GenericSetup includes is that of versioning the specific import steps in a profile. This is not yet implemented, but the idea is that you will be able to look at an existing setup profile to compare the version of each import step with the version at the time the profile was originally applied to your site. Any import steps that were out of date would be able to be reapplied, without the need to perform any of the other steps. There are plenty of details to work out here; I think that the granularity of the versioning is a bit too broad right now, for instance. But I think the idea is a good one.
Not everything that needs to happen during a reinstall or a migration can be represented effectively by a setup profile,
however. Florent Guillaume gave a good example when he described a site that had both News Item and Press Release content types, but which decided they were redundant and wanted to merge them. The setup profile would indicate that there was no more Press Release content type, but there would be no way to specify that the two types had actually merged, and that the existing Press Releases should be switched to News Items. Nuxeo has handled this in an elegant fashion in CPS, by introducing a new ZCML tag (<cps:upgradeStep>) that registers a checker method to verify whether or not the upgrade step needs to happen, and another method which actually performs the upgrade. You can see more about the CPS implementation of this, and a much more low-level description of GenericSetup and how to use it, at Florent's excellent GenericSetup blog post.
I think that by fleshing out the import step versioning implementation, and taking the upgrade step idea that CPS has
implemented and putting it into the GenericSetup core, we would have a good set of tools with which to replace Plone's current product installation and site migration infrastructure. It is also worth noting that the migration infrastructure would work equally well on base and extension profiles, so all add-on products would be able to use the same migration strategy, instead of having to implement their own migration mechanisms as they are forced to do now.
This is not the only possibility, however. I know that Zope3 uses something called "generations" for it's migration needs. I don't know enough about how it works to be able to compare its suitability for Plone, however. I'd love to have a chat w/ someone who understands both generations and Plone, to try to see if we can agree on a best solution. GenericSetup is already, as of Plone 2.5, integral to Plone's site creation process, however, so unless generations turns out to have a radically different and superior approach, I'm currently leaning towards working on the implementation I've described here.