pkgset: Allow filtering modules from Koji tags
Add a configuration option to enable skipping some modules found in the configured tag. Fixes: https://pagure.io/pungi/issue/1260 JIRA: COMPOSE-3794 Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
		
							parent
							
								
									44e551317a
								
							
						
					
					
						commit
						21d45eb243
					
				| @ -700,6 +700,16 @@ Options | ||||
|     See :ref:`additional_packages <additional_packages>` for details about | ||||
|     package specification. | ||||
| 
 | ||||
| **filter_modules** | ||||
|     (*list*) -- modules to be excluded from a variant and architecture; | ||||
|     format: ``[(variant_uid_regex, {arch|*: [name:stream]})]`` | ||||
| 
 | ||||
|     Both name and stream can use shell-style globs. If stream is omitted, all | ||||
|     streams are removed. | ||||
| 
 | ||||
|     This option only applies to modules taken from Koji tags, not modules | ||||
|     explicitly listed in variants XML without any tags. | ||||
| 
 | ||||
| **filter_system_release_packages** | ||||
|     (*bool*) -- for each variant, figure out the best system release package | ||||
|     and filter out all others. This will not work if a variant needs more than | ||||
|  | ||||
| @ -733,6 +733,10 @@ def make_schema(): | ||||
|                 "$ref": "#/definitions/package_mapping", | ||||
|                 "default": [], | ||||
|             }, | ||||
|             "filter_modules": { | ||||
|                 "$ref": "#/definitions/package_mapping", | ||||
|                 "default": [], | ||||
|             }, | ||||
|             "sigkeys": { | ||||
|                 "type": "array", | ||||
|                 "items": {"$ref": "#/definitions/optional_string"}, | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| import os | ||||
| import json | ||||
| import re | ||||
| from fnmatch import fnmatch | ||||
| from itertools import groupby | ||||
| 
 | ||||
| from kobo.rpmlib import parse_nvra | ||||
| @ -26,7 +27,7 @@ import pungi.wrappers.kojiwrapper | ||||
| from pungi.wrappers.comps import CompsWrapper | ||||
| import pungi.phases.pkgset.pkgsets | ||||
| from pungi.arch import getBaseArch | ||||
| from pungi.util import retry, find_old_compose | ||||
| from pungi.util import retry, find_old_compose, get_arch_variant_data | ||||
| from pungi import Modulemd | ||||
| 
 | ||||
| from pungi.phases.pkgset.common import MaterializedPackageSet, get_all_arches | ||||
| @ -190,7 +191,9 @@ def get_pkgset_from_koji(compose, koji_wrapper, path_prefix): | ||||
|     return populate_global_pkgset(compose, koji_wrapper, path_prefix, event_info) | ||||
| 
 | ||||
| 
 | ||||
| def _add_module_to_variant(koji_wrapper, variant, build, add_to_variant_modules=False): | ||||
| def _add_module_to_variant( | ||||
|     koji_wrapper, variant, build, add_to_variant_modules=False, compose=None | ||||
| ): | ||||
|     """ | ||||
|     Adds module defined by Koji build info to variant. | ||||
| 
 | ||||
| @ -198,6 +201,7 @@ def _add_module_to_variant(koji_wrapper, variant, build, add_to_variant_modules= | ||||
|     :param int: build id | ||||
|     :param bool add_to_variant_modules: Adds the modules also to | ||||
|         variant.modules. | ||||
|     :param compose: Compose object to get filters from | ||||
|     """ | ||||
|     mmds = {} | ||||
|     archives = koji_wrapper.koji_proxy.listArchives(build["id"]) | ||||
| @ -225,26 +229,57 @@ def _add_module_to_variant(koji_wrapper, variant, build, add_to_variant_modules= | ||||
|         # longer supported and should be rebuilt. Let's skip it. | ||||
|         return | ||||
| 
 | ||||
|     nsvc = "%(name)s:%(stream)s:%(version)s:%(context)s" % build["extra"]["typeinfo"]["module"] | ||||
|     info = build["extra"]["typeinfo"]["module"] | ||||
|     nsvc = "%(name)s:%(stream)s:%(version)s:%(context)s" % info | ||||
| 
 | ||||
|     added = False | ||||
| 
 | ||||
|     for arch in variant.arches: | ||||
|         if _is_filtered_out(compose, variant, arch, info["name"], info["stream"]): | ||||
|             compose.log_debug("Module %s is filtered from %s.%s", nsvc, variant, arch) | ||||
|             continue | ||||
| 
 | ||||
|         try: | ||||
|             mmd = Modulemd.ModuleStream.read_file( | ||||
|                 mmds["modulemd.%s.txt" % arch], strict=True | ||||
|             ) | ||||
|             variant.arch_mmds.setdefault(arch, {})[nsvc] = mmd | ||||
|             added = True | ||||
|         except KeyError: | ||||
|             # There is no modulemd for this arch. This could mean an arch was | ||||
|             # added to the compose after the module was built. We don't want to | ||||
|             # process this, let's skip this module. | ||||
|             pass | ||||
| 
 | ||||
|     if not added: | ||||
|         # The module is filtered on all arches of this variant. | ||||
|         return None | ||||
| 
 | ||||
|     if add_to_variant_modules: | ||||
|         variant.modules.append({"name": nsvc, "glob": False}) | ||||
| 
 | ||||
|     return nsvc | ||||
| 
 | ||||
| 
 | ||||
| def _is_filtered_out(compose, variant, arch, module_name, module_stream): | ||||
|     """Check if module with given name and stream is filter out from this stream. | ||||
|     """ | ||||
|     if not compose: | ||||
|         return False | ||||
| 
 | ||||
|     for filter in get_arch_variant_data(compose.conf, "filter_modules", arch, variant): | ||||
|         if ":" not in filter: | ||||
|             name_filter = filter | ||||
|             stream_filter = "*" | ||||
|         else: | ||||
|             name_filter, stream_filter = filter.split(":", 1) | ||||
| 
 | ||||
|         if fnmatch(module_name, name_filter) and fnmatch(module_stream, stream_filter): | ||||
|             return True | ||||
| 
 | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| def _get_modules_from_koji( | ||||
|     compose, koji_wrapper, event, variant, variant_tags, tag_to_mmd | ||||
| ): | ||||
| @ -264,26 +299,34 @@ def _get_modules_from_koji( | ||||
|     for module in variant.get_modules(): | ||||
|         koji_modules = get_koji_modules(compose, koji_wrapper, event, module["name"]) | ||||
|         for koji_module in koji_modules: | ||||
|             nsvc = _add_module_to_variant(koji_wrapper, variant, koji_module) | ||||
|             nsvc = _add_module_to_variant( | ||||
|                 koji_wrapper, variant, koji_module, compose=compose | ||||
|             ) | ||||
|             if not nsvc: | ||||
|                 continue | ||||
| 
 | ||||
|             tag = koji_module["tag"] | ||||
|             variant_tags[variant].append(tag) | ||||
| 
 | ||||
|             # Store mapping NSVC --> koji_tag into variant. | ||||
|             # This is needed in createrepo phase where metadata is exposed by producmd | ||||
|             variant.module_uid_to_koji_tag[nsvc] = tag | ||||
| 
 | ||||
|             tag_to_mmd.setdefault(tag, {}) | ||||
|             for arch in variant.arch_mmds: | ||||
|                 tag_to_mmd[tag].setdefault(arch, set()).add(variant.arch_mmds[arch][nsvc]) | ||||
|                 try: | ||||
|                     mmd = variant.arch_mmds[arch][nsvc] | ||||
|                 except KeyError: | ||||
|                     # Module was filtered from here | ||||
|                     continue | ||||
|                 tag_to_mmd[tag].setdefault(arch, set()).add(mmd) | ||||
| 
 | ||||
|             module_msg = ( | ||||
|                 "Module '{uid}' in variant '{variant}' will use Koji tag '{tag}' " | ||||
|                 "(as a result of querying module '{module}')" | ||||
|             ).format(uid=nsvc, variant=variant, tag=tag, module=module["name"]) | ||||
|             compose.log_info("%s" % module_msg) | ||||
|             if tag_to_mmd[tag]: | ||||
|                 compose.log_info( | ||||
|                     "Module '%s' in variant '%s' will use Koji tag '%s' " | ||||
|                     "(as a result of querying module '%s')", | ||||
|                     nsvc, variant, tag, module["name"] | ||||
|                 ) | ||||
| 
 | ||||
|                 # Store mapping NSVC --> koji_tag into variant. This is needed | ||||
|                 # in createrepo phase where metadata is exposed by producmd | ||||
|                 variant.module_uid_to_koji_tag[nsvc] = tag | ||||
| 
 | ||||
| 
 | ||||
| def filter_inherited(koji_proxy, event, module_builds, top_tag): | ||||
| @ -449,21 +492,31 @@ def _get_modules_from_koji_tags( | ||||
| 
 | ||||
|             variant_tags[variant].append(module_tag) | ||||
| 
 | ||||
|             nsvc = _add_module_to_variant(koji_wrapper, variant, build, True) | ||||
|             nsvc = _add_module_to_variant( | ||||
|                 koji_wrapper, variant, build, True, compose=compose | ||||
|             ) | ||||
|             if not nsvc: | ||||
|                 continue | ||||
| 
 | ||||
|             # Store mapping module-uid --> koji_tag into variant. | ||||
|             # This is needed in createrepo phase where metadata is exposed by producmd | ||||
|             variant.module_uid_to_koji_tag[nsvc] = module_tag | ||||
| 
 | ||||
|             tag_to_mmd.setdefault(module_tag, {}) | ||||
|             for arch in variant.arch_mmds: | ||||
|                 tag_to_mmd[module_tag].setdefault(arch, set()).add(variant.arch_mmds[arch][nsvc]) | ||||
|                 try: | ||||
|                     mmd = variant.arch_mmds[arch][nsvc] | ||||
|                 except KeyError: | ||||
|                     # Module was filtered from here | ||||
|                     continue | ||||
|                 tag_to_mmd[module_tag].setdefault(arch, set()).add(mmd) | ||||
| 
 | ||||
|             module_msg = "Module {module} in variant {variant} will use Koji tag {tag}.".format( | ||||
|                 variant=variant, tag=module_tag, module=build["nvr"]) | ||||
|             compose.log_info("%s" % module_msg) | ||||
|             if tag_to_mmd[module_tag]: | ||||
|                 compose.log_info( | ||||
|                     "Module %s in variant %s will use Koji tag %s.", | ||||
|                     nsvc, variant, module_tag | ||||
|                 ) | ||||
| 
 | ||||
|                 # Store mapping module-uid --> koji_tag into variant. This is | ||||
|                 # needed in createrepo phase where metadata is exposed by | ||||
|                 # productmd | ||||
|                 variant.module_uid_to_koji_tag[nsvc] = module_tag | ||||
| 
 | ||||
|     if expected_modules: | ||||
|         # There are some module names that were listed in configuration and not | ||||
| @ -624,7 +677,12 @@ def populate_global_pkgset(compose, koji_wrapper, path_prefix, event): | ||||
|                     # Not current tag, skip it | ||||
|                     continue | ||||
|                 for arch_modules in variant.arch_mmds.values(): | ||||
|                     for rpm_nevra in arch_modules[nsvc].get_rpm_artifacts(): | ||||
|                     try: | ||||
|                         module = arch_modules[nsvc] | ||||
|                     except KeyError: | ||||
|                         # The module was filtered out | ||||
|                         continue | ||||
|                     for rpm_nevra in module.get_rpm_artifacts(): | ||||
|                         nevra = parse_nvra(rpm_nevra) | ||||
|                         modular_packages.add((nevra["name"], nevra["arch"])) | ||||
| 
 | ||||
|  | ||||
| @ -615,9 +615,10 @@ class MockModule(object): | ||||
| 
 | ||||
| @mock.patch("pungi.Modulemd.ModuleStream.read_file", new=MockModule) | ||||
| @unittest.skipIf(Modulemd is None, "Skipping tests, no module support") | ||||
| class TestAddModuleToVariant(unittest.TestCase): | ||||
| class TestAddModuleToVariant(helpers.PungiTestCase): | ||||
| 
 | ||||
|     def setUp(self): | ||||
|         super(TestAddModuleToVariant, self).setUp() | ||||
|         self.koji = mock.Mock() | ||||
|         self.koji.koji_module.pathinfo.typedir.return_value = "/koji" | ||||
|         files = ["modulemd.x86_64.txt", "modulemd.armv7hl.txt", "modulemd.txt"] | ||||
| @ -741,3 +742,61 @@ class TestAddModuleToVariant(unittest.TestCase): | ||||
|                 {"name": "module:master:20190318:abcdef", "glob": False}, | ||||
|             ], | ||||
|         ) | ||||
| 
 | ||||
|     def test_adding_module_but_filtered(self): | ||||
|         compose = helpers.DummyCompose( | ||||
|             self.topdir, {"filter_modules": [(".*", {"*": ["module:*"]})]} | ||||
|         ) | ||||
|         variant = mock.Mock( | ||||
|             arches=["armhfp", "x86_64"], arch_mmds={}, modules=[], uid="Variant" | ||||
|         ) | ||||
| 
 | ||||
|         nsvc = source_koji._add_module_to_variant( | ||||
|             self.koji, | ||||
|             variant, | ||||
|             self.buildinfo, | ||||
|             add_to_variant_modules=True, | ||||
|             compose=compose, | ||||
|         ) | ||||
| 
 | ||||
|         self.assertIsNone(nsvc) | ||||
|         self.assertEqual(variant.arch_mmds, {}) | ||||
|         self.assertEqual(variant.modules, []) | ||||
| 
 | ||||
| 
 | ||||
| class TestIsModuleFiltered(helpers.PungiTestCase): | ||||
|     def assertIsFiltered(self, name, stream): | ||||
|         self.assertTrue( | ||||
|             source_koji._is_filtered_out( | ||||
|                 self.compose, self.compose.variants["Server"], "x86_64", name, stream | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|     def assertIsNotFiltered(self, name, stream): | ||||
|         self.assertFalse( | ||||
|             source_koji._is_filtered_out( | ||||
|                 self.compose, self.compose.variants["Server"], "x86_64", name, stream | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|     def test_no_filters(self): | ||||
|         self.compose = helpers.DummyCompose(self.topdir, {}) | ||||
| 
 | ||||
|         self.assertIsNotFiltered("foo", "master") | ||||
| 
 | ||||
|     def test_filter_by_name(self): | ||||
|         self.compose = helpers.DummyCompose( | ||||
|             self.topdir, {"filter_modules": [(".*", {"*": ["foo"]})]} | ||||
|         ) | ||||
| 
 | ||||
|         self.assertIsFiltered("foo", "master") | ||||
|         self.assertIsNotFiltered("bar", "master") | ||||
| 
 | ||||
|     def test_filter_by_stream(self): | ||||
|         self.compose = helpers.DummyCompose( | ||||
|             self.topdir, {"filter_modules": [(".*", {"*": ["foo:master"]})]} | ||||
|         ) | ||||
| 
 | ||||
|         self.assertIsFiltered("foo", "master") | ||||
|         self.assertIsNotFiltered("bar", "master") | ||||
|         self.assertIsNotFiltered("foo", "stable") | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user