anaconda/anaconda-40.22.3.13/pyanaconda/modules/payloads/payload/dnf/initialization.py
2024-11-14 21:39:56 -08:00

291 lines
10 KiB
Python

#
# Copyright (C) 2020 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.
#
import logging
from collections import namedtuple
import dnf.logging
import libdnf
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.constants import BASE_REPO_NAME, REPO_ORIGIN_SYSTEM
from pyanaconda.core.i18n import _
from pyanaconda.modules.common.errors.payload import SourceSetupError
from pyanaconda.modules.payloads.base.initialization import SetUpSourcesTask, TearDownSourcesTask
from pyanaconda.modules.payloads.constants import SourceType
from pyanaconda.modules.payloads.payload.dnf.dnf_manager import DNFManager, MetadataError
from pyanaconda.modules.payloads.payload.dnf.repositories import update_treeinfo_repositories, \
disable_default_repositories, enable_updates_repositories, create_repository, \
enable_existing_repository, generate_source_from_repository
from pyanaconda.modules.payloads.payload.dnf.tree_info import LoadTreeInfoMetadataTask
DNF_LIBREPO_LOG = "/tmp/dnf.librepo.log"
DNF_LOGGER = "dnf"
log = get_module_logger(__name__)
def configure_dnf_logging():
"""Configure the DNF logging."""
# Set up librepo.
# This is still required even when the librepo has a separate logger because
# DNF needs to have callbacks that the librepo log is written to be able to
# process that log.
libdnf.repo.LibrepoLog.removeAllHandlers()
libdnf.repo.LibrepoLog.addHandler(DNF_LIBREPO_LOG)
# Set up DNF. Increase the log level to the custom DDEBUG level.
dnf_logger = logging.getLogger(DNF_LOGGER)
dnf_logger.setLevel(dnf.logging.DDEBUG)
# The result of the SetUpDNFSourcesTask task.
SetUpDNFSourcesResult = namedtuple(
"LoadTreeInfoMetadataResult", [
"dnf_manager",
"repositories",
"sources",
]
)
class SetUpDNFSourcesTask(SetUpSourcesTask):
"""Set up all the installation source of the DNF payload."""
def __init__(self, sources, repositories, configuration):
"""Create a new task."""
super().__init__(sources)
self._configuration = configuration
self._repositories = repositories
self._treeinfo_repositories = []
self._release_version = None
self._proxy = None
@property
def _source(self):
return self._sources[0]
def run(self):
"""Run the task.
The task returns a named tuple with the following values:
dnf_manager a DNF manager with a configured base
repositories an updated list of additional repositories
sources a list of generated additional sources
:return SetUpDNFSourcesResult: a result of the task
"""
# Set up the main source.
super().run()
# Process the configuration of the main source.
repository = self._process_source_metadata(self._source)
# Process the treeinfo metadata of the main source.
# Update the treeinfo repositories if any.
repositories = update_treeinfo_repositories(
self._repositories,
self._treeinfo_repositories
)
# Configure the DNF manager.
dnf_manager = self._configure_dnf_manager()
# Generate and set up additional sources.
sources = self._generate_additional_sources(
repositories=repositories,
substitute=dnf_manager.substitute
)
self._set_up_sources(sources)
# Load the main source.
self._load_source(dnf_manager, self._source, repository)
# Load additional sources.
self._load_additional_sources(dnf_manager, sources, repositories)
# Validate enabled repositories.
self._validate_repositories(dnf_manager)
# Load package and group metadata.
self.report_progress(_("Downloading group metadata..."))
self._load_metadata(dnf_manager)
return SetUpDNFSourcesResult(
dnf_manager=dnf_manager,
repositories=repositories,
sources=sources,
)
def _process_source_metadata(self, source):
"""Process metadata of a prepared source.
Load treeinfo metadata of the specified source
and process the retrieved results.
:param source: a prepared source with metadata
:return: a resolved repository of the source
"""
if source.type in [SourceType.CDN, SourceType.CLOSEST_MIRROR]:
return
# Generate the initial repository configuration.
repository = source.generate_repo_configuration()
# Load and process treeinfo metadata of the source if any.
task = LoadTreeInfoMetadataTask(repository)
result = task.run()
if result.repository_data:
repository = result.repository_data
if result.treeinfo_repositories:
self._treeinfo_repositories = result.treeinfo_repositories
if result.release_version:
self._release_version = result.release_version
# Change the default proxy configuration.
if repository.proxy:
self._proxy = repository.proxy
# Rename and enable the chosen repository.
repository.name = BASE_REPO_NAME
repository.enabled = True
return repository
@staticmethod
def _generate_additional_sources(repositories, substitute=None):
"""Generate internal sources for additional repositories.
:param repositories: a list of additional repositories
:param function substitute: a substitution function
:return: a list of generated sources
"""
return [
generate_source_from_repository(r, substitute)
for r in repositories
if r.origin != REPO_ORIGIN_SYSTEM
]
def _configure_dnf_manager(self):
"""Create and configure the DNF manager.
Create a new instance of the DNF manager and configure it
based on the provided packages configuration and loaded
source metadata.
:return DNFManager: a configured DNF manager
"""
log.debug("Preparing the DNF base...")
dnf_manager = DNFManager()
dnf_manager.clear_cache()
dnf_manager.configure_base(self._configuration)
dnf_manager.configure_proxy(self._proxy)
dnf_manager.configure_substitution(self._release_version)
dnf_manager.dump_configuration()
dnf_manager.read_system_repositories()
return dnf_manager
def _load_source(self, dnf_manager, source, repository=None):
"""Load the prepared source.
Create, enable or disabled repositories based on the configuration
of the prepared source and its resolved repository data if any.
:param DNFManager dnf_manager: a configured DNF manager
:param source: a prepared installation source
:param repository: a resolved repository of the source
"""
if source.type == SourceType.CDN:
dnf_manager.restore_system_repositories()
disable_default_repositories(dnf_manager)
elif source.type == SourceType.CLOSEST_MIRROR:
dnf_manager.restore_system_repositories()
enable_updates_repositories(dnf_manager, source.updates_enabled)
disable_default_repositories(dnf_manager)
else:
repository = repository or source.generate_repo_configuration()
create_repository(dnf_manager, repository)
def _load_additional_sources(self, dnf_manager, sources, repositories):
"""Load additional sources and handle system repositories.
:param DNFManager dnf_manager: a configured DNF manager
:param sources: a list of prepared additional sources
:param repositories: a list of updated additional repositories
"""
for source in sources:
self._load_source(dnf_manager, source)
for repository in repositories:
if repository.origin == REPO_ORIGIN_SYSTEM:
enable_existing_repository(dnf_manager, repository)
@staticmethod
def _validate_repositories(dnf_manager):
"""Validate all enabled repositories.
Collect error messages about invalid repositories.
All invalid repositories are disabled.
The user repositories are validated when we add them
to DNF, so this covers invalid system repositories.
:param DNFManager dnf_manager: a configured DNF manager
"""
# Check if there is at least one enabled repository.
if not dnf_manager.enabled_repositories:
raise SourceSetupError(_("No repository is configured."))
# Load all enabled repositories and report warnings if any.
for repo_id in dnf_manager.enabled_repositories:
try:
dnf_manager.load_repository(repo_id)
except MetadataError as e:
log.warning(str(e))
@staticmethod
def _load_metadata(dnf_manager):
"""Load metadata of configured repositories.
:param DNFManager dnf_manager: a configured DNF manager
"""
dnf_manager.load_packages_metadata()
dnf_manager.load_repomd_hashes()
class TearDownDNFSourcesTask(TearDownSourcesTask):
"""Tear down all the installation sources of the DNF payload."""
def __init__(self, sources, dnf_manager):
"""Create a new task."""
super().__init__(sources)
self._dnf_manager = dnf_manager
def run(self):
"""Run the task."""
try:
# Tear down the sources.
super().run()
finally:
# Close the DNF base.
self._dnf_manager.reset_base()