Update from upstream #11
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -14,3 +14,5 @@ htmlcov/ | ||||
| .idea/ | ||||
| .tox | ||||
| .venv | ||||
| .kdev4/ | ||||
| pungi.kdev4 | ||||
|  | ||||
| @ -30,9 +30,17 @@ This is a shortened configuration for Fedora Radhide compose as of 2019-10-14. | ||||
|     module_defaults_dir = { | ||||
|         'scm': 'git', | ||||
|         'repo': 'https://pagure.io/releng/fedora-module-defaults.git', | ||||
|         'branch': 'master', | ||||
|         'branch': 'main', | ||||
|         'dir': '.' | ||||
|     } | ||||
|     # Optional module obsoletes configuration which is merged | ||||
|     # into the module index and gets resolved | ||||
|     module_obsoletes_dir = { | ||||
|         'scm': 'git', | ||||
|         'repo': 'https://pagure.io/releng/fedora-module-defaults.git', | ||||
|         'branch': 'main', | ||||
|         'dir': 'obsoletes' | ||||
|     } | ||||
| 
 | ||||
|     variants_file='variants-fedora.xml' | ||||
|     sigkeys = ['12C944D0'] | ||||
|  | ||||
| @ -736,6 +736,7 @@ def make_schema(): | ||||
|                 "patternProperties": {".+": {"$ref": "#/definitions/strings"}}, | ||||
|                 "additionalProperties": False, | ||||
|             }, | ||||
|             "module_obsoletes_dir": {"$ref": "#/definitions/str_or_scm_dict"}, | ||||
|             "create_optional_isos": {"type": "boolean", "default": False}, | ||||
|             "symlink_isos_to": {"type": "string"}, | ||||
|             "dogpile_cache_backend": {"type": "string"}, | ||||
|  | ||||
| @ -377,6 +377,10 @@ class Compose(kobo.log.LoggingBase): | ||||
|     def has_module_defaults(self): | ||||
|         return bool(self.conf.get("module_defaults_dir", False)) | ||||
| 
 | ||||
|     @property | ||||
|     def has_module_obsoletes(self): | ||||
|         return bool(self.conf.get("module_obsoletes_dir", False)) | ||||
| 
 | ||||
|     @property | ||||
|     def config_dir(self): | ||||
|         return os.path.dirname(self.conf._open_file or "") | ||||
|  | ||||
| @ -25,9 +25,10 @@ except (ImportError, ValueError): | ||||
|     Modulemd = None | ||||
| 
 | ||||
| 
 | ||||
| def iter_module_defaults(path): | ||||
| def iter_module_defaults_or_obsoletes(path, obsoletes=False): | ||||
|     """Given a path to a directory with yaml files, yield each module default | ||||
|     in there as a pair (module_name, ModuleDefaults instance). | ||||
|     The same happens for module obsoletes if the obsoletes switch is True. | ||||
|     """ | ||||
|     # It is really tempting to merge all the module indexes into a single one | ||||
|     # and work with it. However that does not allow for detecting conflicting | ||||
| @ -41,7 +42,10 @@ def iter_module_defaults(path): | ||||
|         index = Modulemd.ModuleIndex() | ||||
|         index.update_from_file(file, strict=False) | ||||
|         for module_name in index.get_module_names(): | ||||
|             yield module_name, index.get_module(module_name).get_defaults() | ||||
|             if obsoletes: | ||||
|                 yield module_name, index.get_module(module_name).get_obsoletes() | ||||
|             else: | ||||
|                 yield module_name, index.get_module(module_name).get_defaults() | ||||
| 
 | ||||
| 
 | ||||
| def collect_module_defaults( | ||||
| @ -69,3 +73,21 @@ def collect_module_defaults( | ||||
|             mod_index.add_defaults(defaults) | ||||
| 
 | ||||
|     return mod_index | ||||
| 
 | ||||
| 
 | ||||
| def collect_module_obsoletes(obsoletes_dir, modules_to_load, mod_index=None): | ||||
|     """Load module obsoletes into index. | ||||
| 
 | ||||
|     This works in a similar fashion as collect_module_defaults except the overrides_dir | ||||
|     feature. | ||||
|     """ | ||||
|     mod_index = mod_index or Modulemd.ModuleIndex() | ||||
| 
 | ||||
|     for module_name, obsoletes in iter_module_defaults_or_obsoletes( | ||||
|         obsoletes_dir, obsoletes=True | ||||
|     ): | ||||
|         for obsolete in obsoletes: | ||||
|             if not modules_to_load or module_name in modules_to_load: | ||||
|                 mod_index.add_obsoletes(obsoletes) | ||||
| 
 | ||||
|     return mod_index | ||||
|  | ||||
| @ -509,6 +509,16 @@ class WorkPaths(object): | ||||
|             makedirs(path) | ||||
|         return path | ||||
| 
 | ||||
|     def module_obsoletes_dir(self, create_dir=True): | ||||
|         """ | ||||
|         Example: | ||||
|             work/global/module_obsoletes | ||||
|         """ | ||||
|         path = os.path.join(self.topdir(create_dir=create_dir), "module_obsoletes") | ||||
|         if create_dir: | ||||
|             makedirs(path) | ||||
|         return path | ||||
| 
 | ||||
|     def pkgset_file_cache(self, pkgset_name): | ||||
|         """ | ||||
|         Returns the path to file in which the cached version of | ||||
|  | ||||
| @ -29,7 +29,7 @@ import productmd.rpms | ||||
| from kobo.shortcuts import relative_path, run | ||||
| from kobo.threads import ThreadPool, WorkerThread | ||||
| 
 | ||||
| from ..module_util import Modulemd, collect_module_defaults | ||||
| from ..module_util import Modulemd, collect_module_defaults, collect_module_obsoletes | ||||
| from ..util import ( | ||||
|     get_arch_variant_data, | ||||
|     read_single_module_stream_from_file, | ||||
| @ -266,6 +266,9 @@ def create_variant_repo( | ||||
|             defaults_dir, module_names, mod_index, overrides_dir=overrides_dir | ||||
|         ) | ||||
| 
 | ||||
|         obsoletes_dir = compose.paths.work.module_obsoletes_dir() | ||||
|         collect_module_obsoletes(obsoletes_dir, module_names, mod_index) | ||||
| 
 | ||||
|         # Add extra modulemd files | ||||
|         if variant.uid in compose.conf.get("createrepo_extra_modulemd", {}): | ||||
|             compose.log_debug("Adding extra modulemd for %s.%s", variant.uid, arch) | ||||
|  | ||||
| @ -33,7 +33,11 @@ except ImportError: | ||||
| import pungi.wrappers.kojiwrapper | ||||
| from pungi.arch import get_compatible_arches, split_name_arch | ||||
| from pungi.compose import get_ordered_variant_uids | ||||
| from pungi.module_util import Modulemd, collect_module_defaults | ||||
| from pungi.module_util import ( | ||||
|     Modulemd, | ||||
|     collect_module_defaults, | ||||
|     collect_module_obsoletes, | ||||
| ) | ||||
| from pungi.phases.base import PhaseBase | ||||
| from pungi.phases.createrepo import add_modular_metadata | ||||
| from pungi.util import get_arch_data, get_arch_variant_data, get_variant_data, makedirs | ||||
| @ -698,6 +702,8 @@ def _make_lookaside_repo(compose, variant, arch, pkg_map, package_sets=None): | ||||
|         collect_module_defaults( | ||||
|             defaults_dir, module_names, mod_index, overrides_dir=overrides_dir | ||||
|         ) | ||||
|         obsoletes_dir = compose.paths.work.module_obsoletes_dir() | ||||
|         collect_module_obsoletes(obsoletes_dir, module_names, mod_index) | ||||
| 
 | ||||
|         log_file = compose.paths.log.log_file( | ||||
|             arch, "lookaside_repo_modules_%s" % (variant.uid) | ||||
|  | ||||
| @ -24,7 +24,7 @@ from kobo.threads import run_in_threads | ||||
| from pungi.phases.base import PhaseBase | ||||
| from pungi.phases.gather import write_prepopulate_file | ||||
| from pungi.util import temp_dir | ||||
| from pungi.module_util import iter_module_defaults | ||||
| from pungi.module_util import iter_module_defaults_or_obsoletes | ||||
| from pungi.wrappers.comps import CompsWrapper | ||||
| from pungi.wrappers.createrepo import CreaterepoWrapper | ||||
| from pungi.wrappers.scm import get_dir_from_scm, get_file_from_scm | ||||
| @ -68,10 +68,18 @@ class InitPhase(PhaseBase): | ||||
|         # download module defaults | ||||
|         if self.compose.has_module_defaults: | ||||
|             write_module_defaults(self.compose) | ||||
|             validate_module_defaults( | ||||
|             validate_module_defaults_or_obsoletes( | ||||
|                 self.compose.paths.work.module_defaults_dir(create_dir=False) | ||||
|             ) | ||||
| 
 | ||||
|         # download module obsoletes | ||||
|         if self.compose.has_module_obsoletes: | ||||
|             write_module_obsoletes(self.compose) | ||||
|             validate_module_defaults_or_obsoletes( | ||||
|                 self.compose.paths.work.module_obsoletes_dir(create_dir=False), | ||||
|                 obsoletes=True, | ||||
|             ) | ||||
| 
 | ||||
|         # write prepopulate file | ||||
|         write_prepopulate_file(self.compose) | ||||
| 
 | ||||
| @ -218,28 +226,53 @@ def write_module_defaults(compose): | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| def validate_module_defaults(path): | ||||
| def write_module_obsoletes(compose): | ||||
|     scm_dict = compose.conf["module_obsoletes_dir"] | ||||
|     if isinstance(scm_dict, dict): | ||||
|         if scm_dict["scm"] == "file": | ||||
|             scm_dict["dir"] = os.path.join(compose.config_dir, scm_dict["dir"]) | ||||
|     else: | ||||
|         scm_dict = os.path.join(compose.config_dir, scm_dict) | ||||
| 
 | ||||
|     with temp_dir(prefix="moduleobsoletes_") as tmp_dir: | ||||
|         get_dir_from_scm(scm_dict, tmp_dir, compose=compose) | ||||
|         compose.log_debug("Writing module obsoletes") | ||||
|         shutil.copytree( | ||||
|             tmp_dir, | ||||
|             compose.paths.work.module_obsoletes_dir(create_dir=False), | ||||
|             ignore=shutil.ignore_patterns(".git"), | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| def validate_module_defaults_or_obsoletes(path, obsoletes=False): | ||||
|     """Make sure there are no conflicting defaults. Each module name can only | ||||
|     have one default stream. | ||||
|     have one default stream or module obsolete. | ||||
| 
 | ||||
|     :param str path: directory with cloned module defaults | ||||
|     :param str path: directory with cloned module defaults/obsoletes | ||||
|     """ | ||||
|     seen_defaults = collections.defaultdict(set) | ||||
|     seen = collections.defaultdict(set) | ||||
|     mmd_type = "obsoletes" if obsoletes else "defaults" | ||||
| 
 | ||||
|     for module_name, defaults in iter_module_defaults(path): | ||||
|         seen_defaults[module_name].add(defaults.get_default_stream()) | ||||
|     for module_name, defaults_or_obsoletes in iter_module_defaults_or_obsoletes( | ||||
|         path, obsoletes | ||||
|     ): | ||||
|         if obsoletes: | ||||
|             for obsolete in defaults_or_obsoletes: | ||||
|                 seen[obsolete.props.module_name].add(obsolete) | ||||
|         else: | ||||
|             seen[module_name].add(defaults_or_obsoletes.get_default_stream()) | ||||
| 
 | ||||
|     errors = [] | ||||
|     for module_name, defaults in seen_defaults.items(): | ||||
|         if len(defaults) > 1: | ||||
|     for module_name, defaults_or_obsoletes in seen.items(): | ||||
|         if len(defaults_or_obsoletes) > 1: | ||||
|             errors.append( | ||||
|                 "Module %s has multiple defaults: %s" | ||||
|                 % (module_name, ", ".join(sorted(defaults))) | ||||
|                 "Module %s has multiple %s: %s" | ||||
|                 % (module_name, mmd_type, ", ".join(sorted(defaults_or_obsoletes))) | ||||
|             ) | ||||
| 
 | ||||
|     if errors: | ||||
|         raise RuntimeError( | ||||
|             "There are duplicated module defaults:\n%s" % "\n".join(errors) | ||||
|             "There are duplicated module %s:\n%s" % (mmd_type, "\n".join(errors)) | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -28,7 +28,11 @@ from pungi.util import ( | ||||
|     PartialFuncWorkerThread, | ||||
|     PartialFuncThreadPool, | ||||
| ) | ||||
| from pungi.module_util import Modulemd, collect_module_defaults | ||||
| from pungi.module_util import ( | ||||
|     Modulemd, | ||||
|     collect_module_defaults, | ||||
|     collect_module_obsoletes, | ||||
| ) | ||||
| from pungi.phases.createrepo import add_modular_metadata | ||||
| 
 | ||||
| 
 | ||||
| @ -159,6 +163,9 @@ def _create_arch_repo(worker_thread, args, task_num): | ||||
|         mod_index = collect_module_defaults( | ||||
|             compose.paths.work.module_defaults_dir(), names, overrides_dir=overrides_dir | ||||
|         ) | ||||
|         mod_index = collect_module_obsoletes( | ||||
|             compose.paths.work.module_obsoletes_dir(), names, mod_index | ||||
|         ) | ||||
|         for x in mmd: | ||||
|             mod_index.add_module_stream(x) | ||||
|         add_modular_metadata( | ||||
|  | ||||
| @ -24,7 +24,7 @@ from tests.helpers import ( | ||||
| 
 | ||||
| @mock.patch("pungi.phases.init.run_in_threads", new=fake_run_in_threads) | ||||
| @mock.patch("pungi.phases.init.validate_comps") | ||||
| @mock.patch("pungi.phases.init.validate_module_defaults") | ||||
| @mock.patch("pungi.phases.init.validate_module_defaults_or_obsoletes") | ||||
| @mock.patch("pungi.phases.init.write_module_defaults") | ||||
| @mock.patch("pungi.phases.init.write_global_comps") | ||||
| @mock.patch("pungi.phases.init.write_arch_comps") | ||||
| @ -46,6 +46,7 @@ class TestInitPhase(PungiTestCase): | ||||
|         compose = DummyCompose(self.topdir, {}) | ||||
|         compose.has_comps = True | ||||
|         compose.has_module_defaults = False | ||||
|         compose.has_module_obsoletes = False | ||||
|         compose.setup_optional() | ||||
|         phase = init.InitPhase(compose) | ||||
|         phase.run() | ||||
| @ -100,6 +101,7 @@ class TestInitPhase(PungiTestCase): | ||||
|         compose = DummyCompose(self.topdir, {}) | ||||
|         compose.has_comps = True | ||||
|         compose.has_module_defaults = False | ||||
|         compose.has_module_obsoletes = False | ||||
|         compose.variants["Everything"].groups = [] | ||||
|         compose.variants["Everything"].modules = [] | ||||
|         phase = init.InitPhase(compose) | ||||
| @ -156,6 +158,7 @@ class TestInitPhase(PungiTestCase): | ||||
|         compose = DummyCompose(self.topdir, {}) | ||||
|         compose.has_comps = False | ||||
|         compose.has_module_defaults = False | ||||
|         compose.has_module_obsoletes = False | ||||
|         phase = init.InitPhase(compose) | ||||
|         phase.run() | ||||
| 
 | ||||
| @ -182,6 +185,7 @@ class TestInitPhase(PungiTestCase): | ||||
|         compose = DummyCompose(self.topdir, {}) | ||||
|         compose.has_comps = False | ||||
|         compose.has_module_defaults = True | ||||
|         compose.has_module_obsoletes = False | ||||
|         phase = init.InitPhase(compose) | ||||
|         phase.run() | ||||
| 
 | ||||
| @ -620,13 +624,13 @@ class TestValidateModuleDefaults(PungiTestCase): | ||||
|     def test_valid_files(self): | ||||
|         self._write_defaults({"httpd": ["1"], "python": ["3.6"]}) | ||||
| 
 | ||||
|         init.validate_module_defaults(self.topdir) | ||||
|         init.validate_module_defaults_or_obsoletes(self.topdir) | ||||
| 
 | ||||
|     def test_duplicated_stream(self): | ||||
|         self._write_defaults({"httpd": ["1"], "python": ["3.6", "3.5"]}) | ||||
| 
 | ||||
|         with self.assertRaises(RuntimeError) as ctx: | ||||
|             init.validate_module_defaults(self.topdir) | ||||
|             init.validate_module_defaults_or_obsoletes(self.topdir) | ||||
| 
 | ||||
|         self.assertIn( | ||||
|             "Module python has multiple defaults: 3.5, 3.6", str(ctx.exception) | ||||
| @ -636,7 +640,7 @@ class TestValidateModuleDefaults(PungiTestCase): | ||||
|         self._write_defaults({"httpd": ["1", "2"], "python": ["3.6", "3.5"]}) | ||||
| 
 | ||||
|         with self.assertRaises(RuntimeError) as ctx: | ||||
|             init.validate_module_defaults(self.topdir) | ||||
|             init.validate_module_defaults_or_obsoletes(self.topdir) | ||||
| 
 | ||||
|         self.assertIn("Module httpd has multiple defaults: 1, 2", str(ctx.exception)) | ||||
|         self.assertIn( | ||||
| @ -661,7 +665,7 @@ class TestValidateModuleDefaults(PungiTestCase): | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         init.validate_module_defaults(self.topdir) | ||||
|         init.validate_module_defaults_or_obsoletes(self.topdir) | ||||
| 
 | ||||
| 
 | ||||
| @mock.patch("pungi.phases.init.CompsWrapper") | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user