Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I trigger portal_quickinstaller.reinstallProducts form outside the Plone Site?

Tags:

zope

plone

We're running a Zope server with an eventually large-ish number of Plone (4) sites. Every now and then, an extension product update comes along and requires a re-install to pick up changes in the profile settings, e.g. new content types.

Manually, this would mean clicking through to every Plone site's portal_quickinstaller, tick the products, press update. This is not very feasible if we're talking about dozens of sites, so I'm trying to automate this. Essentially so far, I have the following living as a Script(Python) in the Zope root:

a = context.restrictedTraverse('/')

p = a['Plone']
print p.getSiteManager()
qi = p.restrictedTraverse('portal_quickinstaller')
print qi
qi.reinstallProducts('LinguaPlone')

(Simplified; in reality I have a longer list instead of the single Plone instance, and I might want to reinstall a longer list of products.) This fails with the following:

  Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 613, in uninstallProducts
  Module Products.CMFQuickInstallerTool.InstalledProduct, line 272, in uninstall
  Module Products.CMFQuickInstallerTool.InstalledProduct, line 351, in _cascadeRemove
AttributeError: 'BaseGlobalComponents' object has no attribute 'objectItems'

From my debugging attempts so far, the BaseGlobalComponents is the Zope SiteManager returned by the zope.component.getSiteManager. How do I convince quickinstaller to pick up the right one, i.e. the one from the Plone Site it's living in?

Alternatively, how would I go about automating re-installing products in a way that will remain vaguely feasible for larger installations? (ETA: I'm aware this is not the kind of thing you do automatically with a cronjob, but updates of inhouse-developed products can't be avoided, I'm afraid.)

like image 553
Ulrich Schwarz Avatar asked Dec 16 '22 14:12

Ulrich Schwarz


2 Answers

Here's how to change the active local site manager. You won't be able to do this in Restricted Python, so you'll need to turn your Python script into an External Method or browser view.

from zope.app.component.hooks import setHooks, setSite
setHooks()
setSite(site)

The setHooks call only needs to be done once. In Zope 2.12 these calls should be imported instead from zope.site.hooks and in Zope 2.13 from zope.component.hooks.

Keep in mind that calling reinstallProducts is not appropriate for all add-on products, and not recommended unless you've carefully checked what reinstalling does and are sure it won't cause problems. Some products provide upgrade steps that run actions more selectively.

like image 71
David Glick Avatar answered Jan 31 '23 01:01

David Glick


Disclaimer: are you sure you want to do this? Automatically reinstalling and upgrading products to the latest version, blindly and without any testing on a staging instance, is asking for trouble.

Anyway, you can do such a thing using XML-RPC and a little tweaking. This is how you install a product on a live running instance using XML-RPC:

>>> import xmlrpclib
>>> proxy = xmlrpclib.ServerProxy(
        "http://admin:passwd@localhost:8080/Plone/portal_quickinstaller"
    )
>>> proxy.getProductVersion('Marshall')
'2.0'
>>> proxy.isProductInstalled('Marshall')
'False'
>>> proxy.installProduct('Marshall')
'Registry installed sucessfully.\n'
>>> proxy.isProductInstalled('Marshall')
'True'

To reinstall you need subclass Products.CMFQuickInstallerTool.QuickInstallerTool.py and provide you custom QuickInstallerTool with a method that has the keyword argument "reinstall" set as 'True' by default; something like:

442c442
<                        swallowExceptions=None, reinstall=False,
---
>                        swallowExceptions=None, reinstall=True,
452,457c452,457
<         if self.isProductInstalled(p):
<             prod = self._getOb(p)
<             msg = ('This product is already installed, '
<                    'please uninstall before reinstalling it.')
<             prod.log(msg)
<             return msg
---
>         #if self.isProductInstalled(p):
>         #    prod = self._getOb(p)
>         #    msg = ('This product is already installed, '
>         #           'please uninstall before reinstalling it.')
>         #    prod.log(msg)
>         #    return msg

Even better: provide your own method for gathering information about versions and reinstalling a product, compatible with the XML-RPC protocol (as you cannot pass keyword arguments).

There might be cleaner ways of doing this via XML-RPC, but portal_quickinstaller is not meant to be used in this way and there may be caveats. Use with caution.

like image 27
Rigel Di Scala Avatar answered Jan 31 '23 00:01

Rigel Di Scala