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

487 lines
17 KiB
Python

#
# Kickstart module for DNF payload.
#
# Copyright (C) 2019 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 pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.signal import Signal
from pyanaconda.modules.common.structures.packages import PackagesConfigurationData, \
PackagesSelectionData
from pyanaconda.modules.payloads.constants import PayloadType, SourceType
from pyanaconda.modules.payloads.kickstart import convert_ks_repo_to_repo_data, \
convert_repo_data_to_ks_repo, convert_ks_data_to_packages_selection, \
convert_packages_selection_to_ksdata, convert_ks_data_to_packages_configuration, \
convert_packages_configuration_to_ksdata
from pyanaconda.modules.payloads.payload.dnf.dnf_manager import DNFManager
from pyanaconda.modules.payloads.payload.dnf.utils import protect_installation_devices, \
collect_installation_devices
from pyanaconda.modules.payloads.payload.dnf.validation import CheckPackagesSelectionTask, \
VerifyRepomdHashesTask
from pyanaconda.modules.payloads.payload.dnf.initialization import configure_dnf_logging, \
SetUpDNFSourcesTask, SetUpDNFSourcesResult, TearDownDNFSourcesTask
from pyanaconda.modules.payloads.payload.dnf.installation import SetRPMMacrosTask, \
ResolvePackagesTask, PrepareDownloadLocationTask, DownloadPackagesTask, InstallPackagesTask, \
CleanUpDownloadLocationTask, WriteRepositoriesTask, ImportRPMKeysTask, \
UpdateDNFConfigurationTask
from pyanaconda.modules.payloads.payload.dnf.tear_down import ResetDNFManagerTask
from pyanaconda.modules.payloads.payload.dnf.utils import calculate_required_space
from pyanaconda.modules.payloads.payload.payload_base import PayloadBase
from pyanaconda.modules.payloads.payload.dnf.dnf_interface import DNFInterface
from pyanaconda.modules.payloads.source.factory import SourceFactory
from pyanaconda.modules.payloads.source.utils import has_network_protocol
# Set up the modules logger.
log = get_module_logger(__name__)
# Configure the DNF logging.
configure_dnf_logging()
__all__ = ["DNFModule"]
class DNFModule(PayloadBase):
"""The DNF payload module."""
def __init__(self):
"""Create a DNF module."""
super().__init__()
self._dnf_manager = DNFManager()
self._internal_sources = []
self._repositories = []
self.repositories_changed = Signal()
self._packages_configuration = PackagesConfigurationData()
self.packages_configuration_changed = Signal()
self._packages_selection = PackagesSelectionData()
self.packages_selection_changed = Signal()
self._packages_kickstarted = False
# Protect installation sources.
self._protected_devices = set()
self.sources_changed.connect(self._update_protected_devices)
self.repositories_changed.connect(self._update_protected_devices)
def for_publication(self):
"""Get the interface used to publish this source."""
return DNFInterface(self)
@property
def type(self):
"""Type of this payload."""
return PayloadType.DNF
@property
def default_source_type(self):
"""Type of the default source."""
return SourceType(conf.payload.default_source)
@property
def supported_source_types(self):
"""Get list of sources supported by DNF module."""
return [
SourceType.CDROM,
SourceType.HDD,
SourceType.HMC,
SourceType.NFS,
SourceType.REPO_FILES,
SourceType.REPO_PATH,
SourceType.CLOSEST_MIRROR,
SourceType.CDN,
SourceType.URL
]
def is_network_required(self):
"""Do the sources and repositories require a network?
:return: True or False
"""
# Check sources.
if super().is_network_required():
return True
# Check repositories.
for data in self.repositories:
if data.enabled and has_network_protocol(data.url):
return True
return False
@property
def dnf_manager(self):
"""The DNF manager of this payload."""
return self._dnf_manager
def _set_dnf_manager(self, dnf_manager):
"""Set the DNF manager of this payload."""
self._dnf_manager = dnf_manager
log.debug("The DNF manager is set.")
@property
def repositories(self):
"""The configuration of repositories.
:return [RepoConfigurationData]: a list of repo configurations
"""
return self._repositories
def set_repositories(self, data):
"""Set the configuration of repositories.
:param [RepoConfigurationData] data: a list of repo configurations
"""
self._repositories = data
self.repositories_changed.emit()
log.debug("Repositories are set to: %s", data)
@property
def packages_configuration(self):
"""The packages configuration.
:return: an instance of PackagesConfigurationData
"""
return self._packages_configuration
def set_packages_configuration(self, data):
"""Set the packages configuration.
:param data: an instance of PackagesConfigurationData
"""
self._packages_configuration = data
self.packages_configuration_changed.emit()
log.debug("Packages configuration is set to '%s'.", data)
@property
def packages_selection(self):
"""The packages selection.
:return: an instance of PackagesSelectionData
"""
return self._packages_selection
def set_packages_selection(self, data):
"""Set the packages selection.
:param data: an instance of PackagesSelectionData
"""
self._packages_selection = data
self.packages_selection_changed.emit()
log.debug("Packages selection is set to '%s'.", data)
@property
def packages_kickstarted(self):
"""Are the packages set from a kickstart?
FIXME: This is a temporary property.
:return: True or False
"""
return self._packages_kickstarted
def set_packages_kickstarted(self, value):
"""Are the packages set from a kickstart?"""
self._packages_kickstarted = value
log.debug("Are the packages set from a kickstart? %s", value)
def process_kickstart(self, data):
"""Process the kickstart data."""
self._process_kickstart_sources(data)
self._process_kickstart_repositories(data)
self._process_kickstart_packages(data)
def _process_kickstart_sources(self, data):
"""Process the kickstart sources."""
source_type = SourceFactory.get_rpm_type_for_kickstart(data)
if source_type is None:
return
source = SourceFactory.create_source(source_type)
source.process_kickstart(data)
self.add_source(source)
def _process_kickstart_repositories(self, data):
"""Process the kickstart repositories."""
repositories = list(map(
convert_ks_repo_to_repo_data,
data.repo.dataList()
))
self.set_repositories(repositories)
def _process_kickstart_packages(self, data):
"""Process the kickstart packages."""
selection = convert_ks_data_to_packages_selection(data)
self.set_packages_selection(selection)
self.set_packages_kickstarted(data.packages.seen)
configuration = convert_ks_data_to_packages_configuration(data)
self.set_packages_configuration(configuration)
def setup_kickstart(self, data):
"""Setup the kickstart data."""
self._set_up_kickstart_sources(data)
self._set_up_kickstart_repositories(data)
self._set_up_kickstart_packages(data)
def _set_up_kickstart_sources(self, data):
"""Set up the kickstart sources."""
for source in self.sources:
source.setup_kickstart(data)
def _set_up_kickstart_repositories(self, data):
"""Set up the kickstart repositories."""
# Don't include disabled repositories.
enabled_repositories = list(filter(
lambda r: r.enabled, self.repositories
))
data.repo.repoList = list(map(
convert_repo_data_to_ks_repo,
enabled_repositories
))
def _set_up_kickstart_packages(self, data):
"""Set up the kickstart packages selection."""
convert_packages_selection_to_ksdata(self.packages_selection, data)
convert_packages_configuration_to_ksdata(self.packages_configuration, data)
def _update_protected_devices(self):
"""Protect devices specified by installation sources."""
previous_devices = self._protected_devices
current_devices = collect_installation_devices(
self.sources,
self.repositories,
)
protect_installation_devices(
previous_devices,
current_devices,
)
self._protected_devices = current_devices
def get_available_repositories(self):
"""Get a list of available repositories.
:return: a list with names of available repositories
"""
return self.dnf_manager.repositories
def get_enabled_repositories(self):
"""Get a list of enabled repositories.
:return: a list with names of enabled repositories
"""
return self.dnf_manager.enabled_repositories
def get_default_environment(self):
"""Get a default environment.
:return: an identifier of an environment or an empty string
"""
return self.dnf_manager.default_environment or ""
def get_environments(self):
"""Get a list of environments defined in comps.xml files.
:return: a list with identifiers of environments
"""
return self.dnf_manager.environments
def resolve_environment(self, environment_spec):
"""Translate the given specification to an environment identifier.
:param environment_spec: an environment specification
:return: an identifier of an environment or an empty string
"""
return self.dnf_manager.resolve_environment(environment_spec) or ""
def get_environment_data(self, environment_spec):
"""Get data about the specified environment.
:param environment_spec: an environment specification
:return CompsEnvironmentData: the related environment data
:raise UnknownCompsEnvironmentError: if the environment is unknown
"""
return self.dnf_manager.get_environment_data(environment_spec)
def resolve_group(self, group_spec):
"""Translate the given specification into a group identifier.
:param group_spec: a group specification
:return: an identifier of a group or an empty string
"""
return self.dnf_manager.resolve_group(group_spec) or ""
def get_group_data(self, group_spec):
"""Get data about the specified group.
:param group_spec: a specification of a group
:return CompsGroupData: the related group data
:raise: UnknownCompsGroupError if the group is unknown
"""
return self.dnf_manager.get_group_data(group_spec)
def verify_repomd_hashes_with_task(self):
"""Verify a hash of the repomd.xml file for each enabled repository with a task.
This method tests if URL links from active repositories can be reached.
It is useful when network settings are changed so that we can verify if
repositories are still reachable. The task returns a validation report.
:return: a task
"""
return VerifyRepomdHashesTask(self.dnf_manager)
def validate_packages_selection_with_task(self, data):
"""Validate the specified packages selection.
Return a task for validation of the software selection.
The result of the task is a validation report.
:param PackagesSelectionData data: a packages selection
:return: a task
"""
return CheckPackagesSelectionTask(
dnf_manager=self.dnf_manager,
selection=data,
)
def calculate_required_space(self):
"""Calculate space required for the installation.
:return: required size in bytes
:rtype: int
"""
required_space = calculate_required_space(self.dnf_manager)
return required_space.get_bytes()
def get_repo_configurations(self):
"""Get RepoConfiguration structures for all sources.
These structures will be used by DNF payload in the main process.
FIXME: This is a temporary solution. Will be removed after DNF payload logic is moved.
:return: RepoConfiguration structures for attached sources.
:rtype: RepoConfigurationData instances
"""
return list(filter(None, [s.generate_repo_configuration() for s in self.sources]))
def set_up_sources_with_task(self):
"""Set up installation sources."""
task = SetUpDNFSourcesTask(
sources=self.sources,
repositories=self.repositories,
configuration=self.packages_configuration,
)
task.succeeded_signal.connect(
lambda: self._set_up_sources_on_success(task.get_result())
)
return task
def _set_up_sources_on_success(self, result: SetUpDNFSourcesResult):
"""Update the module based on the configured sources."""
self._set_dnf_manager(result.dnf_manager)
self.set_repositories(result.repositories)
self._internal_sources += result.sources
def tear_down_sources_with_task(self):
"""Tear down installation sources."""
return TearDownDNFSourcesTask(
dnf_manager=self.dnf_manager,
sources=self._internal_sources + self.sources
)
def install_with_tasks(self):
"""Install the payload.
:return: list of tasks
"""
tasks = [
SetRPMMacrosTask(
configuration=self.packages_configuration
),
ResolvePackagesTask(
dnf_manager=self.dnf_manager,
selection=self.packages_selection,
),
PrepareDownloadLocationTask(
dnf_manager=self.dnf_manager,
),
DownloadPackagesTask(
dnf_manager=self.dnf_manager,
),
InstallPackagesTask(
dnf_manager=self.dnf_manager,
),
CleanUpDownloadLocationTask(
dnf_manager=self.dnf_manager,
),
]
self._collect_kernels_on_success(InstallPackagesTask, tasks)
return tasks
def _collect_kernels_on_success(self, task_class, tasks):
"""Collect kernel version lists from a task specified by its class.
Find an instance of the specified task class that should return
a kernel version list if successful. Connect to its signal and
update the kernel version list of the module when the task is
finished.
:param task_class: a class of a scheduled task
:param tasks: a list of scheduled tasks
"""
for task in tasks:
if isinstance(task, task_class):
task.succeeded_signal.connect(
lambda t=task: self.set_kernel_version_list(t.get_result())
)
def post_install_with_tasks(self):
"""Execute post installation steps.
:return: list of tasks
"""
return [
WriteRepositoriesTask(
sysroot=conf.target.system_root,
dnf_manager=self.dnf_manager,
repositories=self.repositories,
),
ImportRPMKeysTask(
sysroot=conf.target.system_root,
gpg_keys=conf.payload.default_rpm_gpg_keys
),
UpdateDNFConfigurationTask(
sysroot=conf.target.system_root,
configuration=self.packages_configuration,
),
ResetDNFManagerTask(
dnf_manager=self.dnf_manager
)
]
def match_available_packages(self, pattern):
"""Find available packages that match the specified pattern.
:param pattern: a pattern for package names
:return: a list of matched package names
"""
return self.dnf_manager.match_available_packages(pattern)