Update from upstream #11
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@ htmlcov/
|
|||||||
.idea/
|
.idea/
|
||||||
.tox
|
.tox
|
||||||
.venv
|
.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 = {
|
module_defaults_dir = {
|
||||||
'scm': 'git',
|
'scm': 'git',
|
||||||
'repo': 'https://pagure.io/releng/fedora-module-defaults.git',
|
'repo': 'https://pagure.io/releng/fedora-module-defaults.git',
|
||||||
'branch': 'master',
|
'branch': 'main',
|
||||||
'dir': '.'
|
'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'
|
variants_file='variants-fedora.xml'
|
||||||
sigkeys = ['12C944D0']
|
sigkeys = ['12C944D0']
|
||||||
|
@ -736,6 +736,7 @@ def make_schema():
|
|||||||
"patternProperties": {".+": {"$ref": "#/definitions/strings"}},
|
"patternProperties": {".+": {"$ref": "#/definitions/strings"}},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
},
|
},
|
||||||
|
"module_obsoletes_dir": {"$ref": "#/definitions/str_or_scm_dict"},
|
||||||
"create_optional_isos": {"type": "boolean", "default": False},
|
"create_optional_isos": {"type": "boolean", "default": False},
|
||||||
"symlink_isos_to": {"type": "string"},
|
"symlink_isos_to": {"type": "string"},
|
||||||
"dogpile_cache_backend": {"type": "string"},
|
"dogpile_cache_backend": {"type": "string"},
|
||||||
|
@ -377,6 +377,10 @@ class Compose(kobo.log.LoggingBase):
|
|||||||
def has_module_defaults(self):
|
def has_module_defaults(self):
|
||||||
return bool(self.conf.get("module_defaults_dir", False))
|
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
|
@property
|
||||||
def config_dir(self):
|
def config_dir(self):
|
||||||
return os.path.dirname(self.conf._open_file or "")
|
return os.path.dirname(self.conf._open_file or "")
|
||||||
|
@ -25,9 +25,10 @@ except (ImportError, ValueError):
|
|||||||
Modulemd = None
|
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
|
"""Given a path to a directory with yaml files, yield each module default
|
||||||
in there as a pair (module_name, ModuleDefaults instance).
|
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
|
# 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
|
# 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 = Modulemd.ModuleIndex()
|
||||||
index.update_from_file(file, strict=False)
|
index.update_from_file(file, strict=False)
|
||||||
for module_name in index.get_module_names():
|
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()
|
yield module_name, index.get_module(module_name).get_defaults()
|
||||||
|
|
||||||
|
|
||||||
@ -69,3 +73,21 @@ def collect_module_defaults(
|
|||||||
mod_index.add_defaults(defaults)
|
mod_index.add_defaults(defaults)
|
||||||
|
|
||||||
return mod_index
|
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)
|
makedirs(path)
|
||||||
return 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):
|
def pkgset_file_cache(self, pkgset_name):
|
||||||
"""
|
"""
|
||||||
Returns the path to file in which the cached version of
|
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.shortcuts import relative_path, run
|
||||||
from kobo.threads import ThreadPool, WorkerThread
|
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 (
|
from ..util import (
|
||||||
get_arch_variant_data,
|
get_arch_variant_data,
|
||||||
read_single_module_stream_from_file,
|
read_single_module_stream_from_file,
|
||||||
@ -266,6 +266,9 @@ def create_variant_repo(
|
|||||||
defaults_dir, module_names, mod_index, overrides_dir=overrides_dir
|
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
|
# Add extra modulemd files
|
||||||
if variant.uid in compose.conf.get("createrepo_extra_modulemd", {}):
|
if variant.uid in compose.conf.get("createrepo_extra_modulemd", {}):
|
||||||
compose.log_debug("Adding extra modulemd for %s.%s", variant.uid, arch)
|
compose.log_debug("Adding extra modulemd for %s.%s", variant.uid, arch)
|
||||||
|
@ -33,7 +33,11 @@ except ImportError:
|
|||||||
import pungi.wrappers.kojiwrapper
|
import pungi.wrappers.kojiwrapper
|
||||||
from pungi.arch import get_compatible_arches, split_name_arch
|
from pungi.arch import get_compatible_arches, split_name_arch
|
||||||
from pungi.compose import get_ordered_variant_uids
|
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.base import PhaseBase
|
||||||
from pungi.phases.createrepo import add_modular_metadata
|
from pungi.phases.createrepo import add_modular_metadata
|
||||||
from pungi.util import get_arch_data, get_arch_variant_data, get_variant_data, makedirs
|
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(
|
collect_module_defaults(
|
||||||
defaults_dir, module_names, mod_index, overrides_dir=overrides_dir
|
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(
|
log_file = compose.paths.log.log_file(
|
||||||
arch, "lookaside_repo_modules_%s" % (variant.uid)
|
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.base import PhaseBase
|
||||||
from pungi.phases.gather import write_prepopulate_file
|
from pungi.phases.gather import write_prepopulate_file
|
||||||
from pungi.util import temp_dir
|
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.comps import CompsWrapper
|
||||||
from pungi.wrappers.createrepo import CreaterepoWrapper
|
from pungi.wrappers.createrepo import CreaterepoWrapper
|
||||||
from pungi.wrappers.scm import get_dir_from_scm, get_file_from_scm
|
from pungi.wrappers.scm import get_dir_from_scm, get_file_from_scm
|
||||||
@ -68,10 +68,18 @@ class InitPhase(PhaseBase):
|
|||||||
# download module defaults
|
# download module defaults
|
||||||
if self.compose.has_module_defaults:
|
if self.compose.has_module_defaults:
|
||||||
write_module_defaults(self.compose)
|
write_module_defaults(self.compose)
|
||||||
validate_module_defaults(
|
validate_module_defaults_or_obsoletes(
|
||||||
self.compose.paths.work.module_defaults_dir(create_dir=False)
|
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
|
||||||
write_prepopulate_file(self.compose)
|
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
|
"""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):
|
for module_name, defaults_or_obsoletes in iter_module_defaults_or_obsoletes(
|
||||||
seen_defaults[module_name].add(defaults.get_default_stream())
|
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 = []
|
errors = []
|
||||||
for module_name, defaults in seen_defaults.items():
|
for module_name, defaults_or_obsoletes in seen.items():
|
||||||
if len(defaults) > 1:
|
if len(defaults_or_obsoletes) > 1:
|
||||||
errors.append(
|
errors.append(
|
||||||
"Module %s has multiple defaults: %s"
|
"Module %s has multiple %s: %s"
|
||||||
% (module_name, ", ".join(sorted(defaults)))
|
% (module_name, mmd_type, ", ".join(sorted(defaults_or_obsoletes)))
|
||||||
)
|
)
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
raise RuntimeError(
|
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,
|
PartialFuncWorkerThread,
|
||||||
PartialFuncThreadPool,
|
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
|
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(
|
mod_index = collect_module_defaults(
|
||||||
compose.paths.work.module_defaults_dir(), names, overrides_dir=overrides_dir
|
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:
|
for x in mmd:
|
||||||
mod_index.add_module_stream(x)
|
mod_index.add_module_stream(x)
|
||||||
add_modular_metadata(
|
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.run_in_threads", new=fake_run_in_threads)
|
||||||
@mock.patch("pungi.phases.init.validate_comps")
|
@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_module_defaults")
|
||||||
@mock.patch("pungi.phases.init.write_global_comps")
|
@mock.patch("pungi.phases.init.write_global_comps")
|
||||||
@mock.patch("pungi.phases.init.write_arch_comps")
|
@mock.patch("pungi.phases.init.write_arch_comps")
|
||||||
@ -46,6 +46,7 @@ class TestInitPhase(PungiTestCase):
|
|||||||
compose = DummyCompose(self.topdir, {})
|
compose = DummyCompose(self.topdir, {})
|
||||||
compose.has_comps = True
|
compose.has_comps = True
|
||||||
compose.has_module_defaults = False
|
compose.has_module_defaults = False
|
||||||
|
compose.has_module_obsoletes = False
|
||||||
compose.setup_optional()
|
compose.setup_optional()
|
||||||
phase = init.InitPhase(compose)
|
phase = init.InitPhase(compose)
|
||||||
phase.run()
|
phase.run()
|
||||||
@ -100,6 +101,7 @@ class TestInitPhase(PungiTestCase):
|
|||||||
compose = DummyCompose(self.topdir, {})
|
compose = DummyCompose(self.topdir, {})
|
||||||
compose.has_comps = True
|
compose.has_comps = True
|
||||||
compose.has_module_defaults = False
|
compose.has_module_defaults = False
|
||||||
|
compose.has_module_obsoletes = False
|
||||||
compose.variants["Everything"].groups = []
|
compose.variants["Everything"].groups = []
|
||||||
compose.variants["Everything"].modules = []
|
compose.variants["Everything"].modules = []
|
||||||
phase = init.InitPhase(compose)
|
phase = init.InitPhase(compose)
|
||||||
@ -156,6 +158,7 @@ class TestInitPhase(PungiTestCase):
|
|||||||
compose = DummyCompose(self.topdir, {})
|
compose = DummyCompose(self.topdir, {})
|
||||||
compose.has_comps = False
|
compose.has_comps = False
|
||||||
compose.has_module_defaults = False
|
compose.has_module_defaults = False
|
||||||
|
compose.has_module_obsoletes = False
|
||||||
phase = init.InitPhase(compose)
|
phase = init.InitPhase(compose)
|
||||||
phase.run()
|
phase.run()
|
||||||
|
|
||||||
@ -182,6 +185,7 @@ class TestInitPhase(PungiTestCase):
|
|||||||
compose = DummyCompose(self.topdir, {})
|
compose = DummyCompose(self.topdir, {})
|
||||||
compose.has_comps = False
|
compose.has_comps = False
|
||||||
compose.has_module_defaults = True
|
compose.has_module_defaults = True
|
||||||
|
compose.has_module_obsoletes = False
|
||||||
phase = init.InitPhase(compose)
|
phase = init.InitPhase(compose)
|
||||||
phase.run()
|
phase.run()
|
||||||
|
|
||||||
@ -620,13 +624,13 @@ class TestValidateModuleDefaults(PungiTestCase):
|
|||||||
def test_valid_files(self):
|
def test_valid_files(self):
|
||||||
self._write_defaults({"httpd": ["1"], "python": ["3.6"]})
|
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):
|
def test_duplicated_stream(self):
|
||||||
self._write_defaults({"httpd": ["1"], "python": ["3.6", "3.5"]})
|
self._write_defaults({"httpd": ["1"], "python": ["3.6", "3.5"]})
|
||||||
|
|
||||||
with self.assertRaises(RuntimeError) as ctx:
|
with self.assertRaises(RuntimeError) as ctx:
|
||||||
init.validate_module_defaults(self.topdir)
|
init.validate_module_defaults_or_obsoletes(self.topdir)
|
||||||
|
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"Module python has multiple defaults: 3.5, 3.6", str(ctx.exception)
|
"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"]})
|
self._write_defaults({"httpd": ["1", "2"], "python": ["3.6", "3.5"]})
|
||||||
|
|
||||||
with self.assertRaises(RuntimeError) as ctx:
|
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("Module httpd has multiple defaults: 1, 2", str(ctx.exception))
|
||||||
self.assertIn(
|
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")
|
@mock.patch("pungi.phases.init.CompsWrapper")
|
||||||
|
Loading…
Reference in New Issue
Block a user