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,6 +42,9 @@ def iter_module_defaults(path):
 | 
			
		||||
        index = Modulemd.ModuleIndex()
 | 
			
		||||
        index.update_from_file(file, strict=False)
 | 
			
		||||
        for module_name in index.get_module_names():
 | 
			
		||||
            if obsoletes:
 | 
			
		||||
                yield module_name, index.get_module(module_name).get_obsoletes()
 | 
			
		||||
            else:
 | 
			
		||||
                yield module_name, index.get_module(module_name).get_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