Repository: plone/Products.CMFPlone

Issue:
addPloneSite swallows exceptions while importing extension profiles
If there's an exception while Products.CMFPlone.factory.addPloneSite is installing an extension profile, it is logged but does not abort execution. This causes problems if addPloneSite is used in a script and something is relying on the exit status of the script to determine whether the site was created successfully (like in https://github.com/plone/plone-backend/blob/5.2.x/skeleton/scripts/create_site.py). It also leads to confusion since it can hide the fact that the site was not fully configured.

Even worse, the exception is obscured in the log because Zope hits a different exception while trying to render a page showing the original exception. This happens because the not-yet-fully-configured Plone site is still set as the active zope.component site.


Discussion:

Resolving patch:
diff --git a/Products/CMFPlone/factory.py b/Products/CMFPlone/factory.py
--- a/Products/CMFPlone/factory.py
+++ b/Products/CMFPlone/factory.py
@@ -1,13 +1,10 @@
 from logging import getLogger
 from plone.registry.interfaces import IRegistry
-from plone.uuid.handlers import addAttributeUUID
-from Products.CMFCore.interfaces import ISiteRoot
 from Products.CMFPlone import PloneMessageFactory as _
 from Products.CMFPlone.events import SiteManagerCreatedEvent
 from plone.base.interfaces import INonInstallable
 from Products.CMFPlone.Portal import PloneSite
 from Products.GenericSetup.tool import SetupTool
-from Products.statusmessages.interfaces import IStatusMessage
 from zope.component import queryUtility
 from zope.component.hooks import setSite
 from zope.event import notify
@@ -143,49 +140,41 @@ def addPloneSite(context, site_id, title='Plone site', description='',
     notify(SiteManagerCreatedEvent(site))
     setSite(site)
 
-    setup_tool.setBaselineContext('profile-%s' % profile_id)
-    setup_tool.runAllImportStepsFromProfile('profile-%s' % profile_id)
-
-    reg = queryUtility(IRegistry, context=site)
-    reg['plone.portal_timezone'] = portal_timezone
-    reg['plone.available_timezones'] = [portal_timezone]
-    reg['plone.default_language'] = default_language
-    reg['plone.available_languages'] = [default_language]
-
-    # Install default content types profile if user do not select "example content"
-    # during site creation.
-    content_types_profile = content_profile_id if setup_content else _TYPES_PROFILE
-
-    setup_tool.runAllImportStepsFromProfile(f'profile-{content_types_profile}')
-
-    props = dict(
-        title=title,
-        description=description,
-    )
-    # Do this before applying extension profiles, so the settings from a
-    # properties.xml file are applied and not overwritten by this
-    site.manage_changeProperties(**props)
-
-    for extension_id in extension_ids:
-        try:
-            setup_tool.runAllImportStepsFromProfile(
-                'profile-%s' % extension_id)
-        except Exception as msg:
-            IStatusMessage(request).add(_(
-                'Could not install ${profile_id}: ${error_msg}! '
-                'Please try to install it manually using the "Addons" '
-                'controlpanel and report any issues to the '
-                'addon maintainers.',
-                mapping={
-                    'profile_id': extension_id,
-                    'error_msg': msg.args,
-                }),
-                type='error')
-            logger.exception(
-                'Error while installing addon {}. '
-                'See traceback below for details.'.format(extension_id))
-
-    if snapshot is True:
-        setup_tool.createSnapshot('initial_configuration')
-
-    return site
+    try:
+        setup_tool.setBaselineContext('profile-%s' % profile_id)
+        setup_tool.runAllImportStepsFromProfile('profile-%s' % profile_id)
+
+        reg = queryUtility(IRegistry, context=site)
+        reg['plone.portal_timezone'] = portal_timezone
+        reg['plone.available_timezones'] = [portal_timezone]
+        reg['plone.default_language'] = default_language
+        reg['plone.available_languages'] = [default_language]
+
+        # Install default content types profile if user do not select "example content"
+        # during site creation.
+        content_types_profile = content_profile_id if setup_content else _TYPES_PROFILE
+
+        setup_tool.runAllImportStepsFromProfile(f'profile-{content_types_profile}')
+
+        props = dict(
+            title=title,
+            description=description,
+        )
+        # Do this before applying extension profiles, so the settings from a
+        # properties.xml file are applied and not overwritten by this
+        site.manage_changeProperties(**props)
+
+        for extension_id in extension_ids:
+            try:
+                setup_tool.runAllImportStepsFromProfile(f"profile-{extension_id}")
+            except Exception:
+                logger.error(f"Error while installing profile {extension_id}:")
+                raise
+
+        if snapshot is True:
+            setup_tool.createSnapshot('initial_configuration')
+
+        return site
+    except Exception:
+        setSite(None)
+        raise


Test patch: