292 lines
10 KiB
Python
292 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()
|