diff --git a/SOURCES/0010-Make-log-rotated-permissions-match-initial-log-permissions-RhBug-1894344.patch b/SOURCES/0010-Make-log-rotated-permissions-match-initial-log-permissions-RhBug-1894344.patch new file mode 100644 index 0000000..3bfdafb --- /dev/null +++ b/SOURCES/0010-Make-log-rotated-permissions-match-initial-log-permissions-RhBug-1894344.patch @@ -0,0 +1,22 @@ +From 04b1a90bb24b7e98d4e001c44f8b3f563ad5f0f6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Tue, 24 Nov 2020 14:31:21 +0100 +Subject: [PATCH] Make rotated log file (mode, owner, group) match previous log + settings (RhBug:1894344) + +https://bugzilla.redhat.com/show_bug.cgi?id=1894344 +--- + etc/logrotate.d/dnf | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/etc/logrotate.d/dnf b/etc/logrotate.d/dnf +index b96c6ff9b4..0ce2629f1b 100644 +--- a/etc/logrotate.d/dnf ++++ b/etc/logrotate.d/dnf +@@ -3,5 +3,5 @@ + notifempty + rotate 4 + weekly +- create 0600 root root ++ create + } diff --git a/SOURCES/0011-Add-new-attribute-for-Package--from-repo.patch b/SOURCES/0011-Add-new-attribute-for-Package--from-repo.patch new file mode 100644 index 0000000..49c2a1a --- /dev/null +++ b/SOURCES/0011-Add-new-attribute-for-Package--from-repo.patch @@ -0,0 +1,117 @@ +From eb2aa8c14208da7a567a0d79a8baa9f5201640cd Mon Sep 17 00:00:00 2001 +From: Jaroslav Mracek +Date: Tue, 24 Nov 2020 09:17:41 +0100 +Subject: [PATCH 1/3] Add `from_repo` attribute for Package class + (RhBug:1898968,1879168) + +It as an alias for private attribute _from_repo. + +https://bugzilla.redhat.com/show_bug.cgi?id=1898968 +https://bugzilla.redhat.com/show_bug.cgi?id=1879168 +--- + dnf/cli/commands/repoquery.py | 2 +- + dnf/package.py | 7 +++++-- + doc/api_package.rst | 6 ++++++ + 3 files changed, 12 insertions(+), 3 deletions(-) + +diff --git a/dnf/cli/commands/repoquery.py b/dnf/cli/commands/repoquery.py +index 099a9312d9..a11b440525 100644 +--- a/dnf/cli/commands/repoquery.py ++++ b/dnf/cli/commands/repoquery.py +@@ -44,7 +44,7 @@ + QFORMAT_MATCH = re.compile(r'%(-?\d*?){([:.\w]+?)}') + + QUERY_TAGS = """\ +-name, arch, epoch, version, release, reponame (repoid), evr, ++name, arch, epoch, version, release, reponame (repoid), from_repo, evr, + debug_name, source_name, source_debug_name, + installtime, buildtime, size, downloadsize, installsize, + provides, requires, obsoletes, conflicts, sourcerpm, +diff --git a/dnf/package.py b/dnf/package.py +index d44ce6706c..f647df6bff 100644 +--- a/dnf/package.py ++++ b/dnf/package.py +@@ -76,12 +76,15 @@ def _from_repo(self): + pkgrepo = None + if self._from_system: + pkgrepo = self.base.history.repo(self) +- else: +- pkgrepo = {} + if pkgrepo: + return '@' + pkgrepo + return self.reponame + ++ @property ++ def from_repo(self): ++ # :api ++ return self._from_repo ++ + @property + def _header(self): + return dnf.rpm._header(self.localPkg()) +diff --git a/doc/api_package.rst b/doc/api_package.rst +index 95df5d4b23..48ef8f1d22 100644 +--- a/doc/api_package.rst ++++ b/doc/api_package.rst +@@ -74,6 +74,12 @@ + + Files the package provides (list of strings). + ++ .. attribute:: from_repo ++ ++ For installed packages returns id of repository from which the package was installed prefixed ++ with '@' (if such information is available in the history database). Otherwise returns id of ++ repository the package belongs to (@System for installed packages of unknown origin) (string). ++ + .. attribute:: group + + Group of the package (string). + +From 1a933f8e036cd704fa6e7f77a8448263e93e540f Mon Sep 17 00:00:00 2001 +From: Jaroslav Mracek +Date: Tue, 24 Nov 2020 09:19:42 +0100 +Subject: [PATCH 2/3] Correct description of Package().reponane attribute + +--- + doc/api_package.rst | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/doc/api_package.rst b/doc/api_package.rst +index 48ef8f1d22..a78897babe 100644 +--- a/doc/api_package.rst ++++ b/doc/api_package.rst +@@ -138,7 +138,7 @@ + + .. attribute:: reponame + +- Id of repository the package was installed from (string). ++ Id of repository the package belongs to (@System for installed packages) (string). + + .. attribute:: requires + + +From 24cdb68776507fdae25bed0e82d80df3018aecfc Mon Sep 17 00:00:00 2001 +From: Jaroslav Mracek +Date: Tue, 24 Nov 2020 09:22:07 +0100 +Subject: [PATCH 3/3] Add unittest for new API + +--- + tests/api/test_dnf_package.py | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/tests/api/test_dnf_package.py b/tests/api/test_dnf_package.py +index 04cddc7ecc..5952352bb5 100644 +--- a/tests/api/test_dnf_package.py ++++ b/tests/api/test_dnf_package.py +@@ -163,6 +163,11 @@ def test_reponame(self): + self.assertHasAttr(self.package, "reponame") + self.assertHasType(self.package.reponame, str) + ++ def test_from_repo(self): ++ # Package.reponame ++ self.assertHasAttr(self.package, "from_repo") ++ self.assertHasType(self.package.from_repo, str) ++ + def test_requires(self): + # Package.requires + self.assertHasAttr(self.package, "requires") diff --git a/SOURCES/0012-Change-behaviour-of-Package-.from-repo.patch b/SOURCES/0012-Change-behaviour-of-Package-.from-repo.patch new file mode 100644 index 0000000..b81646b --- /dev/null +++ b/SOURCES/0012-Change-behaviour-of-Package-.from-repo.patch @@ -0,0 +1,80 @@ +From ca06d200d738fd6b23cb05b9776c9fd29288665f Mon Sep 17 00:00:00 2001 +From: Jaroslav Mracek +Date: Wed, 25 Nov 2020 13:00:22 +0100 +Subject: [PATCH 1/2] Change behaviour of Package().from_repo + +The change makes a difference between private attribute _from_repo and +API attribute. _from_repo is required for `dnf info` and we have to keep +it, but for API the magic handling behind could be confusing. +--- + dnf/package.py | 8 +++++++- + doc/api_package.rst | 5 ++--- + 2 files changed, 9 insertions(+), 4 deletions(-) + +diff --git a/dnf/package.py b/dnf/package.py +index f647df6bff..28ca5ef760 100644 +--- a/dnf/package.py ++++ b/dnf/package.py +@@ -73,6 +73,12 @@ def _from_system(self): + + @property + def _from_repo(self): ++ """ ++ For installed packages returns id of repository from which the package was installed ++ prefixed with '@' (if such information is available in the history database). Otherwise ++ returns id of repository the package belongs to (@System for installed packages of unknown ++ origin) ++ """ + pkgrepo = None + if self._from_system: + pkgrepo = self.base.history.repo(self) +@@ -83,7 +89,7 @@ def _from_repo(self): + @property + def from_repo(self): + # :api +- return self._from_repo ++ return self.base.history.repo(self) + + @property + def _header(self): +diff --git a/doc/api_package.rst b/doc/api_package.rst +index a78897babe..634f504ca6 100644 +--- a/doc/api_package.rst ++++ b/doc/api_package.rst +@@ -76,9 +76,8 @@ + + .. attribute:: from_repo + +- For installed packages returns id of repository from which the package was installed prefixed +- with '@' (if such information is available in the history database). Otherwise returns id of +- repository the package belongs to (@System for installed packages of unknown origin) (string). ++ For installed packages returns id of repository from which the package was installed if such ++ information is available in the history database. Otherwise returns an empty string (string). + + .. attribute:: group + + +From 895e61a1281db753dd28f01c20816e83c5316cdd Mon Sep 17 00:00:00 2001 +From: Jaroslav Mracek +Date: Thu, 26 Nov 2020 10:02:08 +0100 +Subject: [PATCH 2/2] fixup! Change behaviour of Package().from_repo + +--- + dnf/package.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/dnf/package.py b/dnf/package.py +index 28ca5ef760..baef04fa5b 100644 +--- a/dnf/package.py ++++ b/dnf/package.py +@@ -89,7 +89,9 @@ def _from_repo(self): + @property + def from_repo(self): + # :api +- return self.base.history.repo(self) ++ if self._from_system: ++ return self.base.history.repo(self) ++ return "" + + @property + def _header(self): diff --git a/SOURCES/0013-Package-add-a-get-header--method.patch b/SOURCES/0013-Package-add-a-get-header--method.patch new file mode 100644 index 0000000..a5f8478 --- /dev/null +++ b/SOURCES/0013-Package-add-a-get-header--method.patch @@ -0,0 +1,94 @@ +From 38cc67385fb1b36aa0881bc5982bc58d75dac464 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= +Date: Wed, 11 Nov 2020 18:45:11 +0100 +Subject: [PATCH] Package: add a get_header() method (RhBug:1876606) + +Adds get_header() method to the Package class, which returns the rpm +header of an installed package. + += changelog = +msg: Add get_header() method to the Package class +type: enhancement +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1876606 +--- + dnf/package.py | 24 ++++++++++++++++++++++++ + tests/test_package.py | 12 ++++++++++++ + 2 files changed, 36 insertions(+) + +diff --git a/dnf/package.py b/dnf/package.py +index baef04fa5b..836e0e4989 100644 +--- a/dnf/package.py ++++ b/dnf/package.py +@@ -26,11 +26,13 @@ + from dnf.i18n import _ + + import binascii ++import dnf.exceptions + import dnf.rpm + import dnf.yum.misc + import hawkey + import logging + import os ++import rpm + + logger = logging.getLogger("dnf") + +@@ -95,6 +97,11 @@ def from_repo(self): + + @property + def _header(self): ++ """ ++ Returns the header of a locally present rpm package file. As opposed to ++ self.get_header(), which retrieves the header of an installed package ++ from rpmdb. ++ """ + return dnf.rpm._header(self.localPkg()) + + @property +@@ -164,6 +171,23 @@ def debugsource_name(self): + src_name = self.source_name if self.source_name is not None else self.name + return src_name + self.DEBUGSOURCE_SUFFIX + ++ def get_header(self): ++ """ ++ Returns the rpm header of the package if it is installed. If not ++ installed, returns None. The header is not cached, it is retrieved from ++ rpmdb on every call. In case of a failure (e.g. when the rpmdb changes ++ between loading the data and calling this method), raises an instance ++ of PackageNotFoundError. ++ """ ++ if not self._from_system: ++ return None ++ ++ try: ++ # RPMDBI_PACKAGES stands for the header of the package ++ return next(self.base._ts.dbMatch(rpm.RPMDBI_PACKAGES, self.rpmdbid)) ++ except StopIteration: ++ raise dnf.exceptions.PackageNotFoundError("Package not found when attempting to retrieve header", str(self)) ++ + @property + def source_debug_name(self): + # :api +diff --git a/tests/test_package.py b/tests/test_package.py +index cd4872e631..514e5bf099 100644 +--- a/tests/test_package.py ++++ b/tests/test_package.py +@@ -68,6 +68,18 @@ def fn_getter(): + with self.assertRaises(IOError): + pkg._header + ++ # rpm.hdr() is not easy to construct with custom data, we just return a string ++ # instead, as we don't actually need an instance of rpm.hdr for the test ++ @mock.patch("rpm.TransactionSet.dbMatch", lambda self, a, b: iter(["package_header_test_data"])) ++ def test_get_header(self): ++ pkg = self.sack.query().installed().filter(name="pepper")[0] ++ header = pkg.get_header() ++ self.assertEqual(header, "package_header_test_data") ++ ++ pkg = self.sack.query().available().filter(name="pepper")[0] ++ header = pkg.get_header() ++ self.assertEqual(header, None) ++ + @mock.patch("dnf.package.Package.rpmdbid", long(3)) + def test_idx(self): + """ pkg.idx is an int. """ diff --git a/SOURCES/0014-Add-api-function-fill-sack-from-repos-in-cache-RhBug-1865803.patch b/SOURCES/0014-Add-api-function-fill-sack-from-repos-in-cache-RhBug-1865803.patch new file mode 100644 index 0000000..d912d69 --- /dev/null +++ b/SOURCES/0014-Add-api-function-fill-sack-from-repos-in-cache-RhBug-1865803.patch @@ -0,0 +1,115 @@ +From b3542a96c6f77e5cc0b5217e586fcc56fde074d8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Wed, 2 Dec 2020 15:27:13 +0100 +Subject: [PATCH 1/2] Add api function: fill_sack_from_repos_in_cache + (RhBug:1865803) + += changelog = +msg: Add api function fill_sack_from_repos_in_cache to allow loading a repo cache with repomd and (solv file or primary xml) only +type: enhancement +resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1865803 +--- + dnf.spec | 2 +- + dnf/base.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 63 insertions(+), 1 deletion(-) + +diff --git a/dnf/base.py b/dnf/base.py +index 075e74265a..a10b837340 100644 +--- a/dnf/base.py ++++ b/dnf/base.py +@@ -425,6 +425,68 @@ def fill_sack(self, load_system_repo=True, load_available_repos=True): + self._plugins.run_sack() + return self._sack + ++ def fill_sack_from_repos_in_cache(self, load_system_repo=True): ++ # :api ++ """ ++ Prepare Sack and Goal objects and also load all enabled repositories from cache only, ++ it doesn't download anything and it doesn't check if metadata are expired. ++ If there is not enough metadata present (repond.xml or both primary.xml and solv file ++ are missing) given repo is either skipped or it throws a RepoError exception depending ++ on skip_if_unavailable configuration. ++ """ ++ timer = dnf.logging.Timer('sack setup') ++ self.reset(sack=True, goal=True) ++ self._sack = dnf.sack._build_sack(self) ++ lock = dnf.lock.build_metadata_lock(self.conf.cachedir, self.conf.exit_on_lock) ++ with lock: ++ if load_system_repo is not False: ++ try: ++ # FIXME: If build_cache=True, @System.solv is incorrectly updated in install- ++ # remove loops ++ self._sack.load_system_repo(build_cache=False) ++ except IOError: ++ if load_system_repo != 'auto': ++ raise ++ ++ error_repos = [] ++ # Iterate over installed GPG keys and check their validity using DNSSEC ++ if self.conf.gpgkey_dns_verification: ++ dnf.dnssec.RpmImportedKeys.check_imported_keys_validity() ++ for repo in self.repos.iter_enabled(): ++ try: ++ repo._repo.loadCache(throwExcept=True, ignoreMissing=True) ++ mdload_flags = dict(load_filelists=True, ++ load_presto=repo.deltarpm, ++ load_updateinfo=True) ++ if repo.load_metadata_other: ++ mdload_flags["load_other"] = True ++ ++ self._sack.load_repo(repo._repo, **mdload_flags) ++ ++ logger.debug(_("%s: using metadata from %s."), repo.id, ++ dnf.util.normalize_time( ++ repo._repo.getMaxTimestamp())) ++ except (RuntimeError, hawkey.Exception) as e: ++ if repo.skip_if_unavailable is False: ++ raise dnf.exceptions.RepoError( ++ _("loading repo '{}' failure: {}").format(repo.id, e)) ++ else: ++ logger.debug(_("loading repo '{}' failure: {}").format(repo.id, e)) ++ error_repos.append(repo.id) ++ repo.disable() ++ if error_repos: ++ logger.warning( ++ _("Ignoring repositories: %s"), ', '.join(error_repos)) ++ ++ conf = self.conf ++ self._sack._configure(conf.installonlypkgs, conf.installonly_limit, conf.allow_vendor_change) ++ self._setup_excludes_includes() ++ timer() ++ self._goal = dnf.goal.Goal(self._sack) ++ self._goal.protect_running_kernel = conf.protect_running_kernel ++ self._plugins.run_sack() ++ return self._sack ++ + def _finalize_base(self): + self._tempfile_persistor = dnf.persistor.TempfilePersistor( + self.conf.cachedir) + +From 29ae53918d4a0b65a917aca2f8f43416fee15dfd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Thu, 10 Dec 2020 14:54:16 +0100 +Subject: [PATCH 2/2] Add api test for new fill_sack_from_repos_in_cache + +--- + tests/api/test_dnf_base.py | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/tests/api/test_dnf_base.py b/tests/api/test_dnf_base.py +index 656bd22584..335981897e 100644 +--- a/tests/api/test_dnf_base.py ++++ b/tests/api/test_dnf_base.py +@@ -107,6 +107,12 @@ def test_fill_sack(self): + + self.base.fill_sack(load_system_repo=False, load_available_repos=False) + ++ def test_fill_sack_from_repos_in_cache(self): ++ # Base.fill_sack_from_repos_in_cache(self, load_system_repo=True): ++ self.assertHasAttr(self.base, "fill_sack_from_repos_in_cache") ++ ++ self.base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ + def test_close(self): + # Base.close() + self.assertHasAttr(self.base, "close") diff --git a/SOURCES/0015-Add-tests-and-docs-for-fill-sack-from-repos-in-cache-RhBug-1865803.patch b/SOURCES/0015-Add-tests-and-docs-for-fill-sack-from-repos-in-cache-RhBug-1865803.patch new file mode 100644 index 0000000..30dbab6 --- /dev/null +++ b/SOURCES/0015-Add-tests-and-docs-for-fill-sack-from-repos-in-cache-RhBug-1865803.patch @@ -0,0 +1,366 @@ +From a777ff01c79d5e0e2cf3ae7b0652795577253bc3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Thu, 14 Jan 2021 09:58:30 +0100 +Subject: [PATCH 1/3] Fix recreate script + +--- + tests/repos/rpm/recreate | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/repos/rpm/recreate b/tests/repos/rpm/recreate +index da348d9799..0fbb9396bd 100755 +--- a/tests/repos/rpm/recreate ++++ b/tests/repos/rpm/recreate +@@ -1,6 +1,6 @@ + #!/bin/bash + +-THISDIR="$( readlink -f "$( dirname "$0 )" )" ++THISDIR="$( readlink -f "$( dirname "$0" )" )" + cd "$THISDIR" + git rm -rf repodata/ + createrepo --no-database -o . .. + +From 5d4c0266f6967c7cd5f0e675b13fa3e9b395e4dd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Thu, 14 Jan 2021 10:28:53 +0100 +Subject: [PATCH 2/3] Add unit test for fill_sack_from_repos_in_cache + (RhBug:1865803) + +https://bugzilla.redhat.com/show_bug.cgi?id=1865803 +--- + tests/test_fill_sack_from_repos_in_cache.py | 262 ++++++++++++++++++++ + 1 file changed, 262 insertions(+) + create mode 100644 tests/test_fill_sack_from_repos_in_cache.py + +diff --git a/tests/test_fill_sack_from_repos_in_cache.py b/tests/test_fill_sack_from_repos_in_cache.py +new file mode 100644 +index 0000000000..24b0d4598d +--- /dev/null ++++ b/tests/test_fill_sack_from_repos_in_cache.py +@@ -0,0 +1,262 @@ ++# -*- coding: utf-8 -*- ++ ++# Copyright (C) 2012-2021 Red Hat, Inc. ++# ++# This copyrighted material is made available to anyone wishing to use, ++# modify, copy, or redistribute it subject to the terms and conditions of ++# the GNU General Public License v.2, or (at your option) any later version. ++# This program is distributed in the hope that it will be useful, but WITHOUT ++# ANY WARRANTY expressed or implied, including the implied warranties of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General ++# Public License for more details. You should have received a copy of the ++# GNU General Public License along with this program; if not, write to the ++# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA ++# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the ++# source code or documentation are not subject to the GNU General Public ++# License and may only be used or replicated with the express permission of ++# Red Hat, Inc. ++# ++ ++from __future__ import absolute_import ++from __future__ import unicode_literals ++ ++import os ++import tempfile ++import glob ++import shutil ++import unittest ++ ++import dnf.exceptions ++import dnf.repo ++import dnf.sack ++ ++import hawkey ++ ++import tests.support ++from tests.support import mock ++ ++TEST_REPO_NAME = "test-repo" ++ ++ ++class FillSackFromReposInCacheTest(unittest.TestCase): ++ def _create_cache_for_repo(self, repopath, tmpdir): ++ conf = dnf.conf.MainConf() ++ conf.cachedir = os.path.join(tmpdir, "cache") ++ ++ base = dnf.Base(conf=conf) ++ ++ repoconf = dnf.repo.Repo(TEST_REPO_NAME, base.conf) ++ repoconf.baseurl = repopath ++ repoconf.enable() ++ ++ base.repos.add(repoconf) ++ ++ base.fill_sack(load_system_repo=False) ++ base.close() ++ ++ def _setUp_from_repo_path(self, original_repo_path): ++ self.tmpdir = tempfile.mkdtemp(prefix="dnf_test_") ++ ++ self.repo_copy_path = os.path.join(self.tmpdir, "repo") ++ shutil.copytree(original_repo_path, self.repo_copy_path) ++ ++ self._create_cache_for_repo(self.repo_copy_path, self.tmpdir) ++ ++ # Just to be sure remove repo (it shouldn't be used) ++ shutil.rmtree(self.repo_copy_path) ++ ++ # Prepare base for the actual test ++ conf = dnf.conf.MainConf() ++ conf.cachedir = os.path.join(self.tmpdir, "cache") ++ self.test_base = dnf.Base(conf=conf) ++ repoconf = dnf.repo.Repo(TEST_REPO_NAME, conf) ++ repoconf.baseurl = self.repo_copy_path ++ repoconf.enable() ++ self.test_base.repos.add(repoconf) ++ ++ def tearDown(self): ++ self.test_base.close() ++ shutil.rmtree(self.tmpdir) ++ ++ def test_with_solv_solvx_repomd(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove xml metadata except repomd ++ # repomd.xml is not compressed and doesn't end with .gz ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Now we only have cache with just solv, solvx files and repomd.xml ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ self.assertEqual(len(packages), 9) ++ self.assertEqual(packages[0].evr, "4-4") ++ ++ # Use *-updateinfo.solvx ++ adv_pkgs = q.get_advisory_pkgs(hawkey.LT | hawkey.EQ | hawkey.GT) ++ adv_titles = set() ++ for pkg in adv_pkgs: ++ adv_titles.add(pkg.get_advisory(self.test_base.sack).title) ++ self.assertEqual(len(adv_titles), 3) ++ ++ def test_with_just_solv_repomd(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove xml metadata except repomd ++ # repomd.xml is not compressed and doesn't end with .gz ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Remove solvx files ++ solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solvx")) ++ for f in solvx: ++ os.remove(f) ++ ++ # Now we only have cache with just solv files and repomd.xml ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ self.assertEqual(len(packages), 9) ++ self.assertEqual(packages[0].evr, "4-4") ++ ++ # No *-updateinfo.solvx -> we get no advisory packages ++ adv_pkgs = q.get_advisory_pkgs(hawkey.LT | hawkey.EQ | hawkey.GT) ++ self.assertEqual(len(adv_pkgs), 0) ++ ++ def test_with_xml_metadata(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove all solv and solvx files ++ solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solv*")) ++ for f in solvx: ++ os.remove(f) ++ ++ # Now we only have cache with just xml metadata ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ self.assertEqual(len(packages), 9) ++ self.assertEqual(packages[0].evr, "4-4") ++ ++ def test_exception_without_repomd(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove xml metadata ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Now we only have cache with just solv and solvx files ++ # Since we don't have repomd we cannot verify checksums -> fail (exception) ++ ++ self.assertRaises(dnf.exceptions.RepoError, ++ self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False) ++ ++ def test_exception_with_just_repomd(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove xml metadata except repomd ++ # repomd.xml is not compressed and doesn't end with .gz ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Remove all solv and solvx files ++ solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solv*")) ++ for f in solvx: ++ os.remove(f) ++ ++ # Now we only have cache with just repomd ++ # repomd is not enough, it doesn't contain the metadata it self -> fail (exception) ++ ++ self.assertRaises(dnf.exceptions.RepoError, ++ self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False) ++ ++ def test_exception_with_checksum_mismatch_and_only_repomd(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Remove xml metadata except repomd ++ # repomd.xml is not compressed and doesn't end with .gz ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Modify checksum of solv file so it doesn't match with repomd ++ solv = glob.glob(os.path.join(self.tmpdir, "cache/*.solv"))[0] ++ with open(solv, "a") as opensolv: ++ opensolv.write("appended text to change checksum") ++ ++ # Now we only have cache with solvx, modified solv file and just repomd ++ # Since we don't have original xml metadata we cannot regenerate solv -> fail (exception) ++ ++ self.assertRaises(dnf.exceptions.RepoError, ++ self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False) ++ ++ def test_checksum_mistmatch_regenerates_solv(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm")) ++ ++ # Modify checksum of solv file so it doesn't match with repomd ++ solv = glob.glob(os.path.join(self.tmpdir, "cache/*.solv"))[0] ++ with open(solv, "a") as opensolv: ++ opensolv.write("appended text to change checksum") ++ ++ # Now we only have cache with solvx, modified solv file and xml metadata. ++ # Checksum mistmatch causes regeneration of solv file and repo works. ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ self.assertEqual(len(packages), 9) ++ self.assertEqual(packages[0].evr, "4-4") ++ ++ def test_with_modules_yaml(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), ++ "modules/modules/_all/x86_64")) ++ ++ # Now we have full cache (also with modules.yaml) ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ self.assertEqual(len(packages), 8) ++ self.assertEqual(packages[0].evr, "2.02-0.40") ++ ++ self.module_base = dnf.module.module_base.ModuleBase(self.test_base) ++ modules, _ = self.module_base._get_modules("base-runtime*") ++ self.assertEqual(len(modules), 3) ++ self.assertEqual(modules[0].getFullIdentifier(), "base-runtime:f26:1::") ++ ++ def test_with_modular_repo_without_modules_yaml(self): ++ self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), ++ "modules/modules/_all/x86_64")) ++ ++ # Remove xml and yaml metadata except repomd ++ # repomd.xml is not compressed and doesn't end with .gz ++ repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz")) ++ for f in repodata_without_repomd: ++ os.remove(f) ++ ++ # Now we have just solv, *-filenames.solvx and repomd.xml (modules.yaml are not processed into *-modules.solvx) ++ ++ self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ q = self.test_base.sack.query() ++ packages = q.run() ++ # We have many more packages because they are not hidden by modules ++ self.assertEqual(len(packages), 44) ++ self.assertEqual(packages[0].evr, "10.0-7") ++ ++ self.module_base = dnf.module.module_base.ModuleBase(self.test_base) ++ modules, _ = self.module_base._get_modules("base-runtime*") ++ self.assertEqual(len(modules), 0) + +From de6177dba3dc20191e275eec14672570a0c4f4a8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Thu, 14 Jan 2021 12:29:06 +0100 +Subject: [PATCH 3/3] Add docs and examples for fill_sack_from_repos_in_cache + (RhBug:1865803) + +https://bugzilla.redhat.com/show_bug.cgi?id=1865803 +--- + doc/api_base.rst | 41 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 41 insertions(+) + +diff --git a/doc/api_base.rst b/doc/api_base.rst +index 24ecb50e43..f0b1992e88 100644 +--- a/doc/api_base.rst ++++ b/doc/api_base.rst +@@ -111,6 +111,47 @@ + print("id: {}".format(repo.id)) + print("baseurl: {}".format(repo.baseurl)) + ++ .. method:: fill_sack_from_repos_in_cache(load_system_repo=True) ++ ++ Prepare Sack and Goal objects and load all enabled repositories from cache only, it doesn't download anything and it doesn't check if metadata are expired. ++ To successfully load a repository cache it requires repond.xml plus metadata (xml, yaml) or repond.xml plus generated cache files (solv, solvx). ++ If there is not enough metadata given repo is either skipped or it throws a :exc:`dnf.exceptions.RepoError` exception depending on :attr:`dnf.conf.Conf.skip_if_unavailable` configuration. ++ ++ All additional metadata are loaded if present but are not generally required. Note that some metadata like updateinfo.xml get processed into a solvx cache file and its sufficient to have either xml or solvx. Module metadata represented by modules.yaml are not processed therefore they are needed when they are defined in repomd.xml. ++ ++ Example of loading all configured repositories from cache and printing available packages' names:: ++ ++ #!/usr/bin/python3 ++ import dnf ++ ++ with dnf.Base() as base: ++ base.read_all_repos() ++ ++ base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ query = base.sack.query().available() ++ for pkg in query.run(): ++ print(pkg.name) ++ ++ Example of loading a single repository and printing available packages' names without reading repository configuration:: ++ ++ #!/usr/bin/python3 ++ import dnf ++ ++ with dnf.Base() as base: ++ repo = dnf.repo.Repo("rawhide", base.conf) ++ ++ # Repository cache is also identified by its source therefore to find it you need to ++ # set metalink, mirrorlist or baseurl to the same value from which it was created. ++ repo.metalink = "https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=x86_64" ++ ++ base.repos.add(repo) ++ ++ base.fill_sack_from_repos_in_cache(load_system_repo=False) ++ ++ query = base.sack.query().available() ++ for pkg in query.run(): ++ print(pkg.name) + + .. method:: do_transaction([display]) + diff --git a/SOURCES/0016-Run-tests-for-fill-sack-from-repos-in-cache-in-installroot..patch b/SOURCES/0016-Run-tests-for-fill-sack-from-repos-in-cache-in-installroot..patch new file mode 100644 index 0000000..2c8f13b --- /dev/null +++ b/SOURCES/0016-Run-tests-for-fill-sack-from-repos-in-cache-in-installroot..patch @@ -0,0 +1,43 @@ +From 291071a937a1de398641f02002413678398e473c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 8 Feb 2021 08:25:46 +0100 +Subject: [PATCH] Run tests for fill_sack_from_repos_in_cache in installroot + (RhBug:1865803) + +This prevents loading data (like failsafe) from host. + +It also allows testing that there are no modules in the installroot not just +no base-runtime* in test_with_modular_repo_without_modules_yaml. + +https://bugzilla.redhat.com/show_bug.cgi?id=1865803 +--- + tests/test_fill_sack_from_repos_in_cache.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/tests/test_fill_sack_from_repos_in_cache.py b/tests/test_fill_sack_from_repos_in_cache.py +index 24b0d4598d..f27235bf84 100644 +--- a/tests/test_fill_sack_from_repos_in_cache.py ++++ b/tests/test_fill_sack_from_repos_in_cache.py +@@ -42,6 +42,7 @@ class FillSackFromReposInCacheTest(unittest.TestCase): + def _create_cache_for_repo(self, repopath, tmpdir): + conf = dnf.conf.MainConf() + conf.cachedir = os.path.join(tmpdir, "cache") ++ conf.installroot = os.path.join(tmpdir) + + base = dnf.Base(conf=conf) + +@@ -68,6 +69,7 @@ def _setUp_from_repo_path(self, original_repo_path): + # Prepare base for the actual test + conf = dnf.conf.MainConf() + conf.cachedir = os.path.join(self.tmpdir, "cache") ++ conf.installroot = os.path.join(self.tmpdir) + self.test_base = dnf.Base(conf=conf) + repoconf = dnf.repo.Repo(TEST_REPO_NAME, conf) + repoconf.baseurl = self.repo_copy_path +@@ -258,5 +260,5 @@ def test_with_modular_repo_without_modules_yaml(self): + self.assertEqual(packages[0].evr, "10.0-7") + + self.module_base = dnf.module.module_base.ModuleBase(self.test_base) +- modules, _ = self.module_base._get_modules("base-runtime*") ++ modules, _ = self.module_base._get_modules("*") + self.assertEqual(len(modules), 0) diff --git a/SOURCES/0017-Set-persistdir-for-fill-sack-from-repos-in-cache-tests-RhBug-1865803.patch b/SOURCES/0017-Set-persistdir-for-fill-sack-from-repos-in-cache-tests-RhBug-1865803.patch new file mode 100644 index 0000000..b6dee78 --- /dev/null +++ b/SOURCES/0017-Set-persistdir-for-fill-sack-from-repos-in-cache-tests-RhBug-1865803.patch @@ -0,0 +1,61 @@ +From 40e762da5cd2d876b6424f4c25b77e8dc2422a0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Mon, 8 Feb 2021 08:25:46 +0100 +Subject: [PATCH] Set persistdir and substitutions for + fill_sack_from_repos_in_cache tests (RhBug:1865803) + +Setting just installroot is not enough because persistdir is not +automatically prepended with installroot if set via API. + +Also assert exact package names which is more useful output in case the +test fails. + +https://bugzilla.redhat.com/show_bug.cgi?id=1865803 +--- + tests/test_fill_sack_from_repos_in_cache.py | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/tests/test_fill_sack_from_repos_in_cache.py b/tests/test_fill_sack_from_repos_in_cache.py +index f27235bf84..23fd2a4337 100644 +--- a/tests/test_fill_sack_from_repos_in_cache.py ++++ b/tests/test_fill_sack_from_repos_in_cache.py +@@ -42,7 +42,10 @@ class FillSackFromReposInCacheTest(unittest.TestCase): + def _create_cache_for_repo(self, repopath, tmpdir): + conf = dnf.conf.MainConf() + conf.cachedir = os.path.join(tmpdir, "cache") +- conf.installroot = os.path.join(tmpdir) ++ conf.installroot = tmpdir ++ conf.persistdir = os.path.join(conf.installroot, conf.persistdir.lstrip("/")) ++ conf.substitutions["arch"] = "x86_64" ++ conf.substitutions["basearch"] = dnf.rpm.basearch(conf.substitutions["arch"]) + + base = dnf.Base(conf=conf) + +@@ -69,7 +72,10 @@ def _setUp_from_repo_path(self, original_repo_path): + # Prepare base for the actual test + conf = dnf.conf.MainConf() + conf.cachedir = os.path.join(self.tmpdir, "cache") +- conf.installroot = os.path.join(self.tmpdir) ++ conf.installroot = self.tmpdir ++ conf.persistdir = os.path.join(conf.installroot, conf.persistdir.lstrip("/")) ++ conf.substitutions["arch"] = "x86_64" ++ conf.substitutions["basearch"] = dnf.rpm.basearch(conf.substitutions["arch"]) + self.test_base = dnf.Base(conf=conf) + repoconf = dnf.repo.Repo(TEST_REPO_NAME, conf) + repoconf.baseurl = self.repo_copy_path +@@ -231,8 +237,13 @@ def test_with_modules_yaml(self): + + q = self.test_base.sack.query() + packages = q.run() +- self.assertEqual(len(packages), 8) +- self.assertEqual(packages[0].evr, "2.02-0.40") ++ ++ pkg_names = [] ++ for pkg in packages: ++ pkg_names.append(pkg.name) ++ ++ self.assertEqual(pkg_names, ['grub2', 'httpd', 'httpd', 'httpd-doc', 'httpd-doc', 'httpd-provides-name-doc', ++ 'httpd-provides-name-version-release-doc', 'libnghttp2']) + + self.module_base = dnf.module.module_base.ModuleBase(self.test_base) + modules, _ = self.module_base._get_modules("base-runtime*") diff --git a/SOURCES/0018-Allow-stream-switching-if-option-enabled.patch b/SOURCES/0018-Allow-stream-switching-if-option-enabled.patch new file mode 100644 index 0000000..0fd4828 --- /dev/null +++ b/SOURCES/0018-Allow-stream-switching-if-option-enabled.patch @@ -0,0 +1,56 @@ +From 9ceb74f77479910f7844a9a87d4b7623687076be Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= +Date: Fri, 24 Jul 2020 07:59:38 +0200 +Subject: [PATCH] Allow stream switching if option enabled + += changelog = +msg: New config option module_allow_stream_switch allows switching enabled streams +type: enhancement +--- + dnf.spec | 2 +- + dnf/cli/cli.py | 19 ++++++++++--------- + 2 files changed, 11 insertions(+), 10 deletions(-) + +diff --git a/dnf.spec b/dnf.spec +index 0e63b2b422..04f6f104c7 100644 +--- a/dnf.spec ++++ b/dnf.spec +@@ -2,7 +2,7 @@ + %undefine __cmake_in_source_build + + # default dependencies +-%global hawkey_version 0.54.4 ++%global hawkey_version 0.55.0 + %global libcomps_version 0.1.8 + %global libmodulemd_version 1.4.0 + %global rpm_version 4.14.0 +diff --git a/dnf/cli/cli.py b/dnf/cli/cli.py +index be737ed3b7..29d7373fa3 100644 +--- a/dnf/cli/cli.py ++++ b/dnf/cli/cli.py +@@ -166,15 +166,16 @@ def do_transaction(self, display=()): + :return: history database transaction ID or None + """ + if dnf.base.WITH_MODULES: +- switchedModules = dict(self._moduleContainer.getSwitchedStreams()) +- if switchedModules: +- report_module_switch(switchedModules) +- msg = _("It is not possible to switch enabled streams of a module.\n" +- "It is recommended to remove all installed content from the module, and " +- "reset the module using '{prog} module reset ' command. After " +- "you reset the module, you can install the other stream.").format( +- prog=dnf.util.MAIN_PROG) +- raise dnf.exceptions.Error(msg) ++ if not self.conf.module_stream_switch: ++ switchedModules = dict(self._moduleContainer.getSwitchedStreams()) ++ if switchedModules: ++ report_module_switch(switchedModules) ++ msg = _("It is not possible to switch enabled streams of a module.\n" ++ "It is recommended to remove all installed content from the module, and " ++ "reset the module using '{prog} module reset ' command. After " ++ "you reset the module, you can install the other stream.").format( ++ prog=dnf.util.MAIN_PROG) ++ raise dnf.exceptions.Error(msg) + + trans = self.transaction + pkg_str = self.output.list_transaction(trans) diff --git a/SPECS/dnf.spec b/SPECS/dnf.spec index f1f8588..137e6ff 100644 --- a/SPECS/dnf.spec +++ b/SPECS/dnf.spec @@ -1,5 +1,5 @@ # default dependencies -%global hawkey_version 0.55.0 +%global hawkey_version 0.55.0-5 %global libcomps_version 0.1.8 %global libmodulemd_version 1.4.0 %global rpm_version 4.14.2-35 @@ -82,7 +82,7 @@ It supports RPMs, modules and comps groups & environments. Name: dnf Version: 4.4.2 -Release: 6%{?dist} +Release: 10%{?dist} Summary: %{pkg_summary} # For a breakdown of the licensing, see PACKAGE-LICENSING License: GPLv2+ and GPLv2 and GPL @@ -109,6 +109,24 @@ Patch7: 0007-Fix-documentation-of-globs-not-supporting-curly-brackets.pa Patch8: 0008-Module-switch-command.patch # https://github.com/rpm-software-management/dnf/pull/1702 Patch9: 0009-yum.misc.decompress-to-handle-uncompressed-files-RhBug-1895059.patch +# https://github.com/rpm-software-management/dnf/pull/1693 +Patch10: 0010-Make-log-rotated-permissions-match-initial-log-permissions-RhBug-1894344.patch +# https://github.com/rpm-software-management/dnf/pull/1692 +Patch11: 0011-Add-new-attribute-for-Package--from-repo.patch +# https://github.com/rpm-software-management/dnf/pull/1695 +Patch12: 0012-Change-behaviour-of-Package-.from-repo.patch +# https://github.com/rpm-software-management/dnf/pull/1686 +Patch13: 0013-Package-add-a-get-header--method.patch +# https://github.com/rpm-software-management/dnf/pull/1703 +Patch14: 0014-Add-api-function-fill-sack-from-repos-in-cache-RhBug-1865803.patch +# https://github.com/rpm-software-management/dnf/pull/1711 +Patch15: 0015-Add-tests-and-docs-for-fill-sack-from-repos-in-cache-RhBug-1865803.patch +# https://github.com/rpm-software-management/dnf/pull/1721 +Patch16: 0016-Run-tests-for-fill-sack-from-repos-in-cache-in-installroot..patch +#https://github.com/rpm-software-management/dnf/pull/1723 +Patch17: 0017-Set-persistdir-for-fill-sack-from-repos-in-cache-tests-RhBug-1865803.patch +# https://github.com/rpm-software-management/dnf/pull/1725 +Patch18: 0018-Allow-stream-switching-if-option-enabled.patch BuildArch: noarch BuildRequires: cmake @@ -529,9 +547,27 @@ ln -sr %{buildroot}%{confdir}/vars %{buildroot}%{_sysconfdir}/yum/vars %endif %changelog +* Thu Feb 11 2021 Nicola Sella - 4.4.2-10 +- Allow stream switching if option enabled + +* Tue Feb 09 2021 Nicola Sella - 4.4.2-9 +- Set persistdir for fill_sack_from_repos_in_cache tests (RhBug:1865803) + +* Mon Feb 08 2021 Nicola Sella - 4.4.2-8 +- Add api function: fill_sack_from_repos_in_cache (RhBug:1865803) +- Add tests and docs for fill_sack_from_repos_in_cache (RhBug:1865803) +- Run tests for fill_sack_from_repos_in_cache in installroot + +* Fri Feb 05 2021 Nicola Sella - 4.4.2-7 +- Make log rotated permissions match initial log permissions (RhBug:1894344) +- Add new attribute for Package - from_repo +- Change behaviour of Package().from_repo +- Package: add a get_header() method + * Fri Jan 29 2021 Nicola Sella - 4.4.2-6 - yum.misc.decompress() to handle uncompressed files (RhBug:1895059) - Module switch command + * Fri Jan 15 2021 Nicola Sella - 4.4.2-5 - Fix patch for dnf history operations