Skip to content

Commit

Permalink
Merge 133b906 into fa4e38c
Browse files Browse the repository at this point in the history
  • Loading branch information
mauritsvanrees committed Aug 30, 2022
2 parents fa4e38c + 133b906 commit 1f4b6da
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 37 deletions.
11 changes: 9 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
Changelog
=========

2.2.1 (unreleased)
2.3.0 (unreleased)
------------------

- Nothing changed yet.
- Add ``import_path`` to ``upgradeDepends`` directive.
This avoids the need to register a full profile, when you only need
to import a few files once in an upgrade step.
The path must be within the path of a package.
By default this is the path of the profile for which this is an upgrade.
You can also specify a path in a different package: ``other.package:profile/path``.
You can combine this with ``import_steps``, but not with ``import_profile``.
Note that ``metadata.xml`` is never read, so you cannot use profile dependencies.


2.2.0 (2022-04-04)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

setup(
name='Products.GenericSetup',
version='2.2.1.dev0',
version='2.3.0.dev0',
description='Read Zope configuration state from profile dirs / tarballs',
long_description=README + _BOUNDARY + CHANGES,
classifiers=[
Expand Down
39 changes: 36 additions & 3 deletions src/Products/GenericSetup/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,13 @@ def getProfileDependencyChain(profile_id):
"""

def runImportStepFromProfile(profile_id, step_id,
run_dependencies=True, purge_old=None):
run_dependencies=True, purge_old=None,
path=None):
""" Execute a given setup step from the given profile.
o 'profile_id' must be a valid ID of a registered profile;
otherwise, raise KeyError.
But if it is None, we look at the 'path' argument.
o 'step_id' is the ID of the step to run.
Expand All @@ -512,6 +514,12 @@ def runImportStepFromProfile(profile_id, step_id,
o If 'run_dependencies' is True, then run any out-of-date
dependency steps first.
o 'path' is the path to a directory with files to import.
This avoids the need to register a full profile, when you only need
to import a few files once in an upgrade step.
It is best to use an absolute path, otherwise it would depend on
your working directory.
o Return a mapping, with keys:
'steps' -- a sequence of IDs of the steps run.
Expand All @@ -520,13 +528,19 @@ def runImportStepFromProfile(profile_id, step_id,
step
"""

def runAllImportStepsFromProfile(profile_id, purge_old=None,
def runAllImportStepsFromProfile(profile_id,
purge_old=None,
ignore_dependencies=False,
blacklisted_steps=None):
archive=None,
blacklisted_steps=None,
dependency_strategy=None,
path=None):

""" Run all setup steps for the given profile in dependency order.
o 'profile_id' must be a valid ID of a registered profile;
otherwise, raise KeyError.
But if it is None, we look at the 'archive' and 'path' arguments.
o If 'purge_old' is True, then run each step after purging any
"old" setup first (this is the responsibility of the step,
Expand All @@ -535,9 +549,28 @@ def runAllImportStepsFromProfile(profile_id, purge_old=None,
o Unless 'ignore_dependencies' is true this will also import
all profiles this profile depends on.
o 'archive' is a tarball with files to import.
Note that metadata.xml is never read, so you cannot use profile
dependencies.
o 'blacklisted_steps' can be a list of step-names that won't be
executed. Use with special care and only for special cases.
o 'dependency_strategy' is an integer describing what do we do with
already applied dependency profiles. The default is to apply new
profiles (of course), and upgrade outdated ones.
See 'DEFAULT_DEPENDENCY_STRATEGY' in 'tool.py'
o 'path' is the path to a directory with files to import.
This avoids the need to register a full profile, when you only need
to import a few files once in an upgrade step.
It is best to use an absolute path, otherwise it would depend on
your working directory.
Note that metadata.xml is never read, so you cannot use profile
dependencies.
o The profile to be imported is determined from profile_id,
archive or path (the first with a value different from 'None').
o Return a mapping, with keys:
Expand Down
197 changes: 197 additions & 0 deletions src/Products/GenericSetup/tests/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,105 @@ def test_runImportStepFromProfile_consistent_context(self):
purge_old=False)
self.assertFalse(site.purged)

def test_runImportStepFromProfile_with_path_simple(self):
site = self._makeSite()
tool = self._makeOne('setup_tool').__of__(site)
_imported = []

def applyContext(context):
_imported.append(context._profile_path)

tool.applyContext = applyContext
tool.runImportStepFromProfile(None, 'rolemap', path=self._PROFILE_PATH)
self.assertEqual(_imported, [self._PROFILE_PATH])
tool.runImportStepFromProfile(
None, 'rolemap', path=self._PROFILE_PATH2)
self.assertEqual(_imported, [self._PROFILE_PATH, self._PROFILE_PATH2])

def test_runImportStepFromProfile_with_path_no_dependencies(self):
# When importing a path, the metadata.xml is never read, so profile
# dependencies are ignored. Same will be true for archives/tarballs.
# Theoretically this could be fixed. But if you need this, then you
# might as well just register an actual profile.
from ..metadata import METADATA_XML

# Add metadata with dependencies in the directory and register the
# dependency profile.
self._makeFile(METADATA_XML, _METADATA_XML)
site = self._makeSite()
tool = self._makeOne('setup_tool').__of__(site)
profile_registry.registerProfile('bar', 'Bar', '', self._PROFILE_PATH2)

_imported = []

def applyContext(context):
_imported.append(context._profile_path)

tool.applyContext = applyContext
tool.runImportStepFromProfile(
None, 'rolemap', path=self._PROFILE_PATH)
# Only the path of the directory will have been imported,
# not the dependency profile.
self.assertEqual(_imported, [self._PROFILE_PATH])

def test_runImportStepFromProfile_with_path_purging(self):
# Tests for importing a directory with GenericSetup files.
# We are especially interested to see if old settings get purged.
# This is adapted from test_manage_importTarball.
# Note that purging is handled differently for tarballs and
# directories.
site = self._makeSite()
site.setup_tool = self._makeOne('setup_tool')
tool = site.setup_tool
# We need to be Manager to see the result of calling
# manage_importTarball.
newSecurityManager(None, UnrestrictedUser('root', '', ['Manager'], ''))

ROLEMAP_XML = """<?xml version="1.0"?>
<rolemap>
<roles>
<role name="%s" />
</roles>
<permissions />
</rolemap>
"""

def add_rolemap_to_dir(name):
# Create a file rolemap.xml containing 'name' as role.
# Put this in the directory on the profile path.
contents = ROLEMAP_XML % name
if isinstance(contents, six.text_type):
contents = contents.encode('utf-8')
self._makeFile('rolemap.xml', contents)

# Import first role.
add_rolemap_to_dir('First')
tool.runImportStepFromProfile(None, 'rolemap', path=self._PROFILE_PATH)
self.assertTrue('First' in site.valid_roles())

# Import second role.
add_rolemap_to_dir('Second')
tool.runImportStepFromProfile(None, 'rolemap', path=self._PROFILE_PATH)
self.assertTrue('Second' in site.valid_roles())
# The first role is still there, because by default we do not purge.
self.assertTrue('First' in site.valid_roles())

# Import third role in purge mode.
add_rolemap_to_dir('Third')
tool.runImportStepFromProfile(
None, 'rolemap', path=self._PROFILE_PATH, purge_old=True)
self.assertTrue('Third' in site.valid_roles())
# The other roles are gone.
self.assertFalse('First' in site.valid_roles())
self.assertFalse('Second' in site.valid_roles())

# A few standard roles are never removed, probably because they are
# defined one level higher.
self.assertTrue('Anonymous' in site.valid_roles())
self.assertTrue('Authenticated' in site.valid_roles())
self.assertTrue('Manager' in site.valid_roles())
self.assertTrue('Owner' in site.valid_roles())

def test_runAllImportStepsFromProfile_empty(self):

site = self._makeSite()
Expand Down Expand Up @@ -871,6 +970,104 @@ def test_runAllImportStepsFromProfile_pre_post_handlers_functions(self):
self.assertEqual(tool.pre_handler_called, 2)
self.assertEqual(tool.post_handler_called, 2)

def test_runAllImportStepsFromProfile_with_path_simple(self):
site = self._makeSite()
tool = self._makeOne('setup_tool').__of__(site)
_imported = []

def applyContext(context):
_imported.append(context._profile_path)

tool.applyContext = applyContext
tool.runAllImportStepsFromProfile(None, path=self._PROFILE_PATH)
self.assertEqual(_imported, [self._PROFILE_PATH])
tool.runAllImportStepsFromProfile(None, path=self._PROFILE_PATH2)
self.assertEqual(_imported, [self._PROFILE_PATH, self._PROFILE_PATH2])

def test_runAllImportStepsFromProfile_with_path_no_dependencies(self):
# When importing a path, the metadata.xml is never read, so profile
# dependencies are ignored. Same will be true for archives/tarballs.
# Theoretically this could be fixed. But if you need this, then you
# might as well just register an actual profile.
from ..metadata import METADATA_XML

# Add metadata with dependencies in the directory and register the
# dependency profile.
self._makeFile(METADATA_XML, _METADATA_XML)
site = self._makeSite()
tool = self._makeOne('setup_tool').__of__(site)
profile_registry.registerProfile('bar', 'Bar', '', self._PROFILE_PATH2)

_imported = []

def applyContext(context):
_imported.append(context._profile_path)

tool.applyContext = applyContext
tool.runAllImportStepsFromProfile(
None, path=self._PROFILE_PATH)
# Only the path of the directory will have been imported,
# not the dependency profile.
self.assertEqual(_imported, [self._PROFILE_PATH])

def test_runAllImportStepsFromProfile_with_path_purging(self):
# Tests for importing a directory with GenericSetup files.
# We are especially interested to see if old settings get purged.
# This is adapted from test_manage_importTarball.
# Note that purging is handled differently for tarballs and
# directories.
site = self._makeSite()
site.setup_tool = self._makeOne('setup_tool')
tool = site.setup_tool
# We need to be Manager to see the result of calling
# manage_importTarball.
newSecurityManager(None, UnrestrictedUser('root', '', ['Manager'], ''))

ROLEMAP_XML = """<?xml version="1.0"?>
<rolemap>
<roles>
<role name="%s" />
</roles>
<permissions />
</rolemap>
"""

def add_rolemap_to_dir(name):
# Create a file rolemap.xml containing 'name' as role.
# Put this in the directory on the profile path.
contents = ROLEMAP_XML % name
if isinstance(contents, six.text_type):
contents = contents.encode('utf-8')
self._makeFile('rolemap.xml', contents)

# Import first role.
add_rolemap_to_dir('First')
tool.runAllImportStepsFromProfile(None, path=self._PROFILE_PATH)
self.assertTrue('First' in site.valid_roles())

# Import second role.
add_rolemap_to_dir('Second')
tool.runAllImportStepsFromProfile(None, path=self._PROFILE_PATH)
self.assertTrue('Second' in site.valid_roles())
# The first role is still there, because by default we do not purge.
self.assertTrue('First' in site.valid_roles())

# Import third role in purge mode.
add_rolemap_to_dir('Third')
tool.runAllImportStepsFromProfile(None, path=self._PROFILE_PATH,
purge_old=True)
self.assertTrue('Third' in site.valid_roles())
# The other roles are gone.
self.assertFalse('First' in site.valid_roles())
self.assertFalse('Second' in site.valid_roles())

# A few standard roles are never removed, probably because they are
# defined one level higher.
self.assertTrue('Anonymous' in site.valid_roles())
self.assertTrue('Authenticated' in site.valid_roles())
self.assertTrue('Manager' in site.valid_roles())
self.assertTrue('Owner' in site.valid_roles())

def test_runExportStep_nonesuch(self):
site = self._makeSite()
tool = self._makeOne('setup_tool').__of__(site)
Expand Down
16 changes: 12 additions & 4 deletions src/Products/GenericSetup/tests/test_zcml.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ def test_registerUpgradeSteps(self):
... run_deps="True"
... purge="True"
... />
... <genericsetup:upgradeDepends
... title="Bar Upgrade dependency profile import steps"
... import_path="dummy-path"
... />
... </genericsetup:upgradeSteps>
... </configure>'''
>>> zcml.load_config('meta.zcml', Products.GenericSetup)
Expand All @@ -306,11 +310,13 @@ def test_registerUpgradeSteps(self):
>>> isinstance(steps, list)
True
>>> len(steps)
3
>>> step1, step2, step3 = steps
>>> step1['source'] == step2['source'] == step3['source'] == ('1', '0')
4
>>> step1, step2, step3, step4 = steps
>>> step1['source'] == step2['source'] == step3['source'] \
== step4['source'] == ('1', '0')
True
>>> step1['dest'] == step2['dest'] == step3['dest'] == ('1', '1')
>>> step1['dest'] == step2['dest'] == step3['dest'] \
== step4['dest'] == ('1', '1')
True
>>> step1['step'].handler
<function b_dummy_upgrade at ...>
Expand All @@ -328,6 +334,8 @@ def test_registerUpgradeSteps(self):
True
>>> step3['step'].purge
True
>>> str(step4['step'].import_path)
'dummy-path'
First one listed should be second in the registry due to sortkey:
Expand Down

0 comments on commit 1f4b6da

Please sign in to comment.