Update Vendors patch against upstream 47fce173e75408d9a7a26225d389161caf72e244 (0.23.0-1)

The package version 0.23.0-1.elevate.2
This commit is contained in:
Yuriy Kohut 2025-09-30 10:32:05 +03:00
parent 15fd026e53
commit 2f853fc90e
2 changed files with 941 additions and 1 deletions

View File

@ -3633,6 +3633,217 @@ index 56a94b5d..46c5d9b6 100755
mount -o "remount,$old_opts" "$NEWROOT" mount -o "remount,$old_opts" "$NEWROOT"
exit $result exit $result
- -
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh
index d73060cb..45f98148 100755
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh
+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/module-setup.sh
@@ -102,7 +102,6 @@ install() {
inst_binary grep
# script to actually run the upgrader binary
- inst_hook upgrade 49 "$_moddir/mount_usr.sh"
inst_hook upgrade 50 "$_moddir/do-upgrade.sh"
#NOTE: some clean up?.. ideally, everything should be inside the leapp*
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/mount_usr.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/mount_usr.sh
deleted file mode 100755
index 9366ac13..00000000
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/mount_usr.sh
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/bin/sh
-# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
-# ex: ts=8 sw=4 sts=4 et filetype=sh
-
-type info >/dev/null 2>&1 || . /lib/dracut-lib.sh
-
-export NEWROOT=${NEWROOT:-"/sysroot"}
-
-filtersubvol() {
- _oldifs="$IFS"
- IFS=","
- set "$@"
- IFS="$_oldifs"
- while [ $# -gt 0 ]; do
- case $1 in
- subvol=*) :;;
- *) printf '%s' "${1}," ;;
- esac
- shift
- done
-}
-
-mount_usr()
-{
- #
- # mount_usr [true | false]
- # Expected a "true" value for the last attempt to mount /usr. On the last
- # attempt, in case of failure drop to shell.
- #
- # Return 0 when everything is all right
- # In case of failure and /usr has been detected:
- # return 2 when $1 is "true" (drop to shell invoked)
- # (note: possibly it's nonsense, but to be sure..)
- # return 1 otherwise
- #
- _last_attempt="$1"
- # check, if we have to mount the /usr filesystem
- while read -r _dev _mp _fs _opts _freq _passno; do
- [ "${_dev%%#*}" != "$_dev" ] && continue
- if [ "$_mp" = "/usr" ]; then
- case "$_dev" in
- LABEL=*)
- _dev="$(echo "$_dev" | sed 's,/,\\x2f,g')"
- _dev="/dev/disk/by-label/${_dev#LABEL=}"
- ;;
- UUID=*)
- _dev="${_dev#block:}"
- _dev="/dev/disk/by-uuid/${_dev#UUID=}"
- ;;
- esac
-
- # shellcheck disable=SC2154 # Variable root is assigned by dracut
- _root_dev=${root#block:}
-
- if strstr "$_opts" "subvol=" && \
- [ "$(stat -c '%D:%i' "$_root_dev")" = "$(stat -c '%D:%i' "$_dev")" ] && \
- [ -n "$rflags" ]; then
- # for btrfs subvolumes we have to mount /usr with the same rflags
- rflags=$(filtersubvol "$rflags")
- rflags=${rflags%%,}
- _opts="${_opts:+${_opts},}${rflags}"
- elif getargbool 0 ro; then
- # if "ro" is specified, we want /usr to be mounted read-only
- _opts="${_opts:+${_opts},}ro"
- elif getargbool 0 rw; then
- # if "rw" is specified, we want /usr to be mounted read-write
- _opts="${_opts:+${_opts},}rw"
- fi
- echo "$_dev ${NEWROOT}${_mp} $_fs ${_opts} $_freq $_passno"
- _usr_found="1"
- break
- fi
- done < "${NEWROOT}/etc/fstab" >> /etc/fstab
-
- if [ "$_usr_found" = "" ]; then
- # nothing to do
- return 0
- fi
-
- info "Mounting /usr with -o $_opts"
- mount "${NEWROOT}/usr" 2>&1 | vinfo
- mount -o remount,rw "${NEWROOT}/usr"
-
- if ismounted "${NEWROOT}/usr"; then
- # success!!
- return 0
- fi
-
- if [ "$_last_attempt" = "true" ]; then
- warn "Mounting /usr to ${NEWROOT}/usr failed"
- warn "*** Dropping you to a shell; the system will continue"
- warn "*** when you leave the shell."
- action_on_fail
- return 2
- fi
-
- return 1
-}
-
-
-try_to_mount_usr() {
- _last_attempt="$1"
- if [ ! -f "${NEWROOT}/etc/fstab" ]; then
- warn "File ${NEWROOT}/etc/fstab doesn't exist."
- return 1
- fi
-
- # In case we have the LVM command available try make it activate all partitions
- if command -v lvm 2>/dev/null 1>/dev/null; then
- lvm vgchange --sysinit -a y || {
- warn "Detected problem when tried to activate LVM VG."
- if [ "$_last_attempt" != "true" ]; then
- # this is not last execution, retry
- return 1
- fi
- # NOTE(pstodulk):
- # last execution, so call mount_usr anyway
- # I am not 100% about lvm vgchange exit codes and I am aware of
- # possible warnings, in this last run, let's keep it on mount_usr
- # anyway..
- }
- fi
-
- mount_usr "$1"
-}
-
-_sleep_timeout=15
-_last_attempt="false"
-for i in 0 1 2 3 4 5 6 7 8 9 10 11; do
- info "Storage initialisation: Attempt $i of 11. Wait $_sleep_timeout seconds."
- sleep $_sleep_timeout
- if [ $i -eq 11 ]; then
- _last_attempt="true"
- fi
- try_to_mount_usr "$_last_attempt" && break
-
- # something is wrong. In some cases, storage needs more time for the
- # initialisation - especially in case of SAN.
-
- if [ "$_last_attempt" = "true" ]; then
- warn "The last attempt to initialize storage has not been successful."
- warn "Unknown state of the storage. It is possible that upgrade will be stopped."
- break
- fi
-
- warn "Failed attempt to initialize the storage. Retry..."
-done
-
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/initrd-cleanup-override.conf b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/initrd-cleanup-override.conf
new file mode 100644
index 00000000..d24e0ef0
--- /dev/null
+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/initrd-cleanup-override.conf
@@ -0,0 +1,3 @@
+[Service]
+ExecStart=
+ExecStart=-/usr/bin/true
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/module-setup.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/module-setup.sh
index 06479fb5..30ae57b3 100755
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/module-setup.sh
+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/module-setup.sh
@@ -54,6 +54,17 @@ install() {
ln -sf "../${s}.service" "$upgrade_wantsdir"
done
+ # Setup modified initrd-cleanup.service in the upgrade initramfs to enable
+ # storage initialisation using systemd-fstab-generator. We want to run the
+ # initrd-parse-etc.service but this one triggers also the initrd-cleanup.service
+ # which triggers the switch-root and isolated actions that basically kills
+ # the original upgrade service when used.
+ # The initrd-parse-etc.service has different content across RHEL systems,
+ # so we override rather initrd-cleanup.service instead as we do not need
+ # that one for the upgrade process.
+ mkdir -p "${unitdir}/initrd-cleanup.service.d"
+ inst_simple "${_moddir}/initrd-cleanup-override.conf" "${unitdir}/initrd-cleanup.service.d/initrd-cleanup-override.conf"
+
# just try : set another services into the wantsdir
# sysroot.mount \
# dracut-mount \
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/upgrade.target b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/upgrade.target
index 366b5cab..d2bf7313 100644
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/upgrade.target
+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/90sys-upgrade/upgrade.target
@@ -2,7 +2,7 @@
Description=System Upgrade
Documentation=man:upgrade.target(7)
# ##sysinit.target sockets.target initrd-root-fs.target initrd-root-device.target initrd-fs.target
-Wants=initrd-root-fs.target initrd-root-device.target initrd-fs.target initrd-usr-fs.target
+Wants=initrd-root-fs.target initrd-root-device.target initrd-fs.target initrd-usr-fs.target initrd-parse-etc.service
Requires=basic.target sysroot.mount
-After=basic.target sysroot.mount
+After=basic.target sysroot.mount initrd-fs.target
AllowIsolate=yes
diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py diff --git a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py b/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py
index 003f3fc5..9e7bbf4a 100644 index 003f3fc5..9e7bbf4a 100644
--- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py --- a/repos/system_upgrade/common/actors/distributionsignedrpmscanner/actor.py
@ -3837,6 +4048,732 @@ index 582a5821..18f2c33f 100644
+ to_reinstall=list(to_reinstall), + to_reinstall=list(to_reinstall),
modules_to_reset=list(modules_to_reset.values()), modules_to_reset=list(modules_to_reset.values()),
modules_to_enable=list(modules_to_enable.values()))) modules_to_enable=list(modules_to_enable.values())))
diff --git a/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/actor.py b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/actor.py
new file mode 100644
index 00000000..aba60645
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/actor.py
@@ -0,0 +1,21 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import enable_lvm_autoactivation as enable_lvm_autoactivation_lib
+from leapp.models import DistributionSignedRPM, UpgradeInitramfsTasks
+from leapp.tags import FactsPhaseTag, IPUWorkflowTag
+
+
+class EnableLVMAutoactivation(Actor):
+ """
+ Enable LVM autoactivation in upgrade initramfs.
+
+ Produce instructions for upgrade initramfs generation that will result in LVM
+ autoactivation in the initramfs.
+ """
+
+ name = 'enable_lvm_autoactivation'
+ consumes = (DistributionSignedRPM,)
+ produces = (UpgradeInitramfsTasks, )
+ tags = (FactsPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ enable_lvm_autoactivation_lib.emit_lvm_autoactivation_instructions()
diff --git a/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/libraries/enable_lvm_autoactivation.py b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/libraries/enable_lvm_autoactivation.py
new file mode 100644
index 00000000..e312277b
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/libraries/enable_lvm_autoactivation.py
@@ -0,0 +1,21 @@
+from leapp.libraries.common.rpms import has_package
+from leapp.libraries.stdlib import api
+from leapp.models import DistributionSignedRPM, UpgradeInitramfsTasks
+
+
+def emit_lvm_autoactivation_instructions():
+ if not has_package(DistributionSignedRPM, 'lvm2'):
+ api.current_logger().debug(
+ 'Upgrade initramfs will not autoenable LVM devices - `lvm2` RPM is not installed.'
+ )
+ return
+
+ # the 69-dm-lvm.rules trigger pvscan and vgchange when LVM device is detected
+ files_to_include = [
+ '/usr/sbin/pvscan',
+ '/usr/sbin/vgchange',
+ '/usr/lib/udev/rules.d/69-dm-lvm.rules'
+ ]
+ lvm_autoactivation_instructions = UpgradeInitramfsTasks(include_files=files_to_include)
+
+ api.produce(lvm_autoactivation_instructions)
diff --git a/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/tests/test_lvm_autoactivation_enablement.py b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/tests/test_lvm_autoactivation_enablement.py
new file mode 100644
index 00000000..c5150aea
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/enable_lvm_autoactivation/tests/test_lvm_autoactivation_enablement.py
@@ -0,0 +1,50 @@
+from leapp.libraries.actor import enable_lvm_autoactivation
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import DistributionSignedRPM, RPM, UpgradeInitramfsTasks
+
+
+def test_emit_lvm_autoactivation_instructions_produces_correct_message(monkeypatch):
+ """Test that emit_lvm_autoactivation_instructions produces UpgradeInitramfsTasks with correct files."""
+ lvm_package = RPM(
+ name='lvm2',
+ version='2',
+ release='1',
+ epoch='1',
+ packager='',
+ arch='x86_64',
+ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51'
+ )
+
+ msgs = [
+ DistributionSignedRPM(items=[lvm_package])
+ ]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ enable_lvm_autoactivation.emit_lvm_autoactivation_instructions()
+
+ assert api.produce.called == 1
+
+ produced_msg = api.produce.model_instances[0]
+
+ assert isinstance(produced_msg, UpgradeInitramfsTasks)
+
+ expected_files = [
+ '/usr/sbin/pvscan',
+ '/usr/sbin/vgchange',
+ '/usr/lib/udev/rules.d/69-dm-lvm.rules'
+ ]
+ assert produced_msg.include_files == expected_files
+
+
+def test_no_action_if_lvm_rpm_missing(monkeypatch):
+ msgs = [
+ DistributionSignedRPM(items=[])
+ ]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ enable_lvm_autoactivation.emit_lvm_autoactivation_instructions()
+
+ assert api.produce.called == 0
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py
new file mode 100644
index 00000000..5fe25515
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py
@@ -0,0 +1,22 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import mount_unit_generator as mount_unit_generator_lib
+from leapp.models import TargetUserSpaceInfo, UpgradeInitramfsTasks
+from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag
+
+
+class MountUnitGenerator(Actor):
+ """
+ Sets up storage initialization using systemd's mount units in the upgrade container.
+ """
+
+ name = 'mount_unit_generator'
+ consumes = (
+ TargetUserSpaceInfo,
+ )
+ produces = (
+ UpgradeInitramfsTasks,
+ )
+ tags = (IPUWorkflowTag, InterimPreparationPhaseTag)
+
+ def process(self):
+ mount_unit_generator_lib.setup_storage_initialization()
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
new file mode 100644
index 00000000..e1060559
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
@@ -0,0 +1,307 @@
+import os
+import shutil
+import tempfile
+
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common import mounting
+from leapp.libraries.stdlib import api, CalledProcessError, run
+from leapp.models import TargetUserSpaceInfo, UpgradeInitramfsTasks
+
+
+def run_systemd_fstab_generator(output_directory):
+ api.current_logger().debug(
+ 'Generating mount units for the source system into {}'.format(output_directory)
+ )
+
+ try:
+ generator_cmd = [
+ '/usr/lib/systemd/system-generators/systemd-fstab-generator',
+ output_directory,
+ output_directory,
+ output_directory
+ ]
+ run(generator_cmd)
+ except CalledProcessError as error:
+ api.current_logger().error(
+ 'Failed to generate mount units using systemd-fstab-generator. Error: {}'.format(error)
+ )
+ details = {'details': str(error)}
+ raise StopActorExecutionError(
+ 'Failed to generate mount units using systemd-fstab-generator',
+ details
+ )
+
+ api.current_logger().debug(
+ 'Mount units successfully generated into {}'.format(output_directory)
+ )
+
+
+def _read_unit_file_lines(unit_file_path): # Encapsulate IO for tests
+ with open(unit_file_path) as unit_file:
+ return unit_file.readlines()
+
+
+def _write_unit_file_lines(unit_file_path, lines): # Encapsulate IO for tests
+ with open(unit_file_path, 'w') as unit_file:
+ unit_file.write('\n'.join(lines) + '\n')
+
+
+def _delete_file(file_path):
+ os.unlink(file_path)
+
+
+def _prefix_mount_unit_with_sysroot(mount_unit_path, new_unit_destination):
+ """
+ Prefix the mount target with /sysroot as expected in the upgrade initramfs.
+
+ A new mount unit file is written to new_unit_destination.
+ """
+ # NOTE(pstodulk): Note that right now we update just the 'Where' key, however
+ # what about RequiresMountsFor, .. there could be some hidden dragons.
+ # In case of issues, investigate these values in generated unit files.
+ api.current_logger().debug(
+ 'Prefixing {}\'s mount target with /sysroot. Output will be written to {}'.format(
+ mount_unit_path,
+ new_unit_destination
+ )
+ )
+ unit_lines = _read_unit_file_lines(mount_unit_path)
+
+ output_lines = []
+ for line in unit_lines:
+ line = line.strip()
+ if not line.startswith('Where='):
+ output_lines.append(line)
+ continue
+
+ _, destination = line.split('=', 1)
+ new_destination = os.path.join('/sysroot', destination.lstrip('/'))
+
+ output_lines.append('Where={}'.format(new_destination))
+
+ _write_unit_file_lines(new_unit_destination, output_lines)
+
+ api.current_logger().debug(
+ 'Done. Modified mount unit successfully written to {}'.format(new_unit_destination)
+ )
+
+
+def prefix_all_mount_units_with_sysroot(dir_containing_units):
+ for unit_file_path in os.listdir(dir_containing_units):
+ # systemd requires mount path to be in the unit name
+ modified_unit_destination = 'sysroot-{}'.format(unit_file_path)
+ modified_unit_destination = os.path.join(dir_containing_units, modified_unit_destination)
+
+ unit_file_path = os.path.join(dir_containing_units, unit_file_path)
+
+ if not unit_file_path.endswith('.mount'):
+ api.current_logger().debug(
+ 'Skipping {} when prefixing mount units with /sysroot - not a mount unit.'.format(
+ unit_file_path
+ )
+ )
+ continue
+
+ _prefix_mount_unit_with_sysroot(unit_file_path, modified_unit_destination)
+
+ _delete_file(unit_file_path)
+ api.current_logger().debug('Original mount unit {} removed.'.format(unit_file_path))
+
+
+def _fix_symlinks_in_dir(dir_containing_mount_units, target_dir):
+ """
+ Fix broken symlinks in given target_dir due to us modifying (renaming) the mount units.
+
+ The target_dir contains symlinks to the (mount) units that are required
+ in order for the local-fs.target to be reached. However, we renamed these units to reflect
+ that we have changed their mount destinations by prefixing the mount destination with /sysroot.
+ Hence, we regenerate the symlinks.
+ """
+
+ target_dir_path = os.path.join(dir_containing_mount_units, target_dir)
+ if not os.path.exists(target_dir_path):
+ api.current_logger().debug(
+ 'The {} directory does not exist. Skipping'
+ .format(target_dir)
+ )
+ return
+
+ api.current_logger().debug(
+ 'Removing the old {} directory from {}.'
+ .format(target_dir, dir_containing_mount_units)
+ )
+
+ shutil.rmtree(target_dir_path)
+ os.mkdir(target_dir_path)
+
+ api.current_logger().debug('Populating {} with new symlinks.'.format(target_dir))
+
+ for unit_file in os.listdir(dir_containing_mount_units):
+ if not unit_file.endswith('.mount'):
+ continue
+
+ place_fastlink_at = os.path.join(target_dir_path, unit_file)
+ fastlink_points_to = os.path.join('../', unit_file)
+ try:
+ run(['ln', '-s', fastlink_points_to, place_fastlink_at])
+
+ api.current_logger().debug(
+ 'Dependency on {} created.'.format(unit_file)
+ )
+ except CalledProcessError as err:
+ err_descr = (
+ 'Failed to create required unit dependencies under {} for the upgrade initramfs.'
+ .format(target_dir)
+ )
+ details = {'details': str(err)}
+ raise StopActorExecutionError(err_descr, details=details)
+
+
+def fix_symlinks_in_targets(dir_containing_mount_units):
+ """
+ Fix broken symlinks in *.target.* directories caused by earlier modified mount units.
+
+ Generated mount unit files are part of one of systemd targets (list below),
+ which means that a symlink from a systemd target to exists for each of
+ them. Based on this, systemd knows when (local or remote file systems?)
+ they must (".requires" suffix") or could (".wants" suffix) be mounted.
+ See the man 5 systemd.mount for more details how mount units are split into
+ these targets.
+
+ The list of possible target directories where these mount units could end:
+ * local-fs.target.requires
+ * local-fs.target.wants
+ * local-fs-pre.target.requires
+ * local-fs-pre.target.wants
+ * remote-fs.target.requires
+ * remote-fs.target.wants
+ * remote-fs-pre.target.requires
+ * remote-fs-pre.target.wants
+ Most likely, unit files are not generated for "*pre*" targets, but to be
+ sure really. Longer list does not cause any issues in this code.
+
+ In most cases, "local-fs.target.requires" is the only important directory
+ for us during the upgrade. But in some (sometimes common) cases we will
+ need some of the others as well.
+
+ These directories do not have to necessarily exists if there are no mount
+ unit files that could be put there. But most likely "local-fs.target.requires"
+ will always exists.
+ """
+ dir_list = [
+ 'local-fs.target.requires',
+ 'local-fs.target.wants',
+ 'local-fs-pre.target.requires',
+ 'local-fs-pre.target.wants',
+ 'remote-fs.target.requires',
+ 'remote-fs.target.wants',
+ 'remote-fs-pre.target.requires',
+ 'remote-fs-pre.target.wants',
+ ]
+ for tdir in dir_list:
+ _fix_symlinks_in_dir(dir_containing_mount_units, tdir)
+
+
+def copy_units_into_system_location(upgrade_container_ctx, dir_with_our_mount_units):
+ """
+ Copy units and their .wants/.requires directories into the target userspace container.
+
+ :return: A list of files in the target userspace that were created by copying.
+ :rtype: list[str]
+ """
+ dest_inside_container = '/usr/lib/systemd/system'
+
+ api.current_logger().debug(
+ 'Copying generated mount units for upgrade from {} to {}'.format(
+ dir_with_our_mount_units,
+ upgrade_container_ctx.full_path(dest_inside_container)
+ )
+ )
+
+ copied_files = []
+ prefix_len_to_drop = len(upgrade_container_ctx.base_dir)
+
+ # We cannot rely on mounting library when copying into container
+ # as we want to control what happens to symlinks and
+ # shutil.copytree in Python3.6 fails if dst directory exists already
+ # - which happens in some cases when copying these files.
+ for root, dummy_dirs, files in os.walk(dir_with_our_mount_units):
+ rel_path = os.path.relpath(root, dir_with_our_mount_units)
+ if rel_path == '.':
+ rel_path = ''
+ dst_dir = os.path.join(upgrade_container_ctx.full_path(dest_inside_container), rel_path)
+ os.makedirs(dst_dir, mode=0o755, exist_ok=True)
+
+ for file in files:
+ src_file = os.path.join(root, file)
+ dst_file = os.path.join(dst_dir, file)
+ api.current_logger().debug(
+ 'Copying mount unit file {} to {}'.format(src_file, dst_file)
+ )
+ if os.path.islink(dst_file):
+ # If the target file already exists and it is a symlink, it will
+ # fail and we want to overwrite this.
+ # NOTE(pstodulk): You could think that it cannot happen, but
+ # in future possibly it could happen, so let's rather be careful
+ # and handle it. If the dst file exists, we want to overwrite it
+ # for sure
+ _delete_file(dst_file)
+ shutil.copy2(src_file, dst_file, follow_symlinks=False)
+ copied_files.append(dst_file[prefix_len_to_drop:])
+
+ return copied_files
+
+
+def remove_units_for_targets_that_are_already_mounted_by_dracut(dir_with_our_mount_units):
+ """
+ Remove mount units for mount targets that are already mounted by dracut.
+
+ Namely, remove mount units:
+ '-.mount' (mounts /)
+ 'usr.mount' (mounts /usr)
+ """
+
+ # NOTE: remount-fs.service creates dependency cycles that are nondeterministically broken
+ # by systemd, causing unpredictable failures. The service is supposed to remount root
+ # and /usr, reapplying mount options from /etc/fstab. However, the fstab file present in
+ # the initramfs is not the fstab from the source system, and, therefore, it is pointless
+ # to require the service. It would make sense after we switched root during normal boot
+ # process.
+ already_mounted_units = [
+ '-.mount',
+ 'usr.mount',
+ 'local-fs.target.wants/systemd-remount-fs.service'
+ ]
+
+ for unit in already_mounted_units:
+ unit_location = os.path.join(dir_with_our_mount_units, unit)
+
+ if not os.path.exists(unit_location):
+ api.current_logger().debug('The {} unit does not exists, no need to remove it.'.format(unit))
+ continue
+
+ _delete_file(unit_location)
+
+
+def request_units_inclusion_in_initramfs(files_to_include):
+ api.current_logger().debug('Including the following files into initramfs: {}'.format(files_to_include))
+
+ additional_files = [
+ '/usr/sbin/swapon' # If the system has swap, we have also generated a swap unit to activate it
+ ]
+
+ tasks = UpgradeInitramfsTasks(include_files=files_to_include + additional_files)
+ api.produce(tasks)
+
+
+def setup_storage_initialization():
+ userspace_info = next(api.consume(TargetUserSpaceInfo), None)
+
+ with mounting.NspawnActions(base_dir=userspace_info.path) as upgrade_container_ctx:
+ with tempfile.TemporaryDirectory(dir='/var/lib/leapp/', prefix='tmp_systemd_fstab_') as workspace_path:
+ run_systemd_fstab_generator(workspace_path)
+ remove_units_for_targets_that_are_already_mounted_by_dracut(workspace_path)
+ prefix_all_mount_units_with_sysroot(workspace_path)
+ fix_symlinks_in_targets(workspace_path)
+ mount_unit_files = copy_units_into_system_location(upgrade_container_ctx, workspace_path)
+ request_units_inclusion_in_initramfs(mount_unit_files)
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
new file mode 100644
index 00000000..b814f6ce
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
@@ -0,0 +1,269 @@
+import os
+import shutil
+
+import pytest
+
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.actor import mount_unit_generator
+from leapp.libraries.common.testutils import logger_mocked
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import TargetUserSpaceInfo, UpgradeInitramfsTasks
+
+
+def test_run_systemd_fstab_generator_successful_generation(monkeypatch):
+ """Test successful mount unit generation."""
+
+ output_dir = '/tmp/test_output'
+ expected_cmd = [
+ '/usr/lib/systemd/system-generators/systemd-fstab-generator',
+ output_dir,
+ output_dir,
+ output_dir
+ ]
+
+ def mock_run(command):
+ assert command == expected_cmd
+
+ return {
+ "stdout": "",
+ "stderr": "",
+ "exit_code": 0,
+ }
+
+ monkeypatch.setattr(mount_unit_generator, 'run', mock_run)
+ mount_unit_generator.run_systemd_fstab_generator(output_dir)
+
+
+def test_run_systemd_fstab_generator_failure(monkeypatch):
+ """Test handling of systemd-fstab-generator failure."""
+ output_dir = '/tmp/test_output'
+ expected_cmd = [
+ '/usr/lib/systemd/system-generators/systemd-fstab-generator',
+ output_dir,
+ output_dir,
+ output_dir
+ ]
+
+ def mock_run(command):
+ assert command == expected_cmd
+ raise CalledProcessError(message='Generator failed', command=['test'], result={'exit_code': 1})
+
+ monkeypatch.setattr(mount_unit_generator, 'run', mock_run)
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ with pytest.raises(StopActorExecutionError):
+ mount_unit_generator.run_systemd_fstab_generator(output_dir)
+
+
+def test_prefix_mount_unit_with_sysroot(monkeypatch):
+ """Test prefixing a single mount unit with /sysroot."""
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ input_content = [
+ "[Unit]\n",
+ "Description=Test Mount\n",
+ "[Mount]\n",
+ "Where=/home\n",
+ "What=/dev/sda1\n"
+ ]
+
+ expected_output_lines = [
+ "[Unit]",
+ "Description=Test Mount",
+ "[Mount]",
+ "Where=/sysroot/home",
+ "What=/dev/sda1"
+ ]
+
+ def mock_read_unit_file_lines(unit_file_path):
+ return input_content
+
+ def mock_write_unit_file_lines(unit_file_path, lines):
+ assert unit_file_path == '/test/output.mount'
+ assert lines == expected_output_lines
+
+ monkeypatch.setattr(mount_unit_generator, '_read_unit_file_lines', mock_read_unit_file_lines)
+ monkeypatch.setattr(mount_unit_generator, '_write_unit_file_lines', mock_write_unit_file_lines)
+
+ mount_unit_generator._prefix_mount_unit_with_sysroot(
+ '/test/input.mount',
+ '/test/output.mount'
+ )
+
+
+def test_prefix_all_mount_units_with_sysroot(monkeypatch):
+ """Test prefixing all mount units in a directory."""
+
+ expected_changes = {
+ '/test/dir/home.mount': {
+ 'new_unit_destination': '/test/dir/sysroot-home.mount',
+ 'should_be_deleted': True,
+ 'deleted': False,
+ },
+ '/test/dir/var.mount': {
+ 'new_unit_destination': '/test/dir/sysroot-var.mount',
+ 'should_be_deleted': True,
+ 'deleted': False,
+ },
+ '/test/dir/not-a-mount.service': {
+ 'new_unit_destination': None,
+ 'should_be_deleted': False,
+ 'deleted': False,
+ }
+ }
+
+ def mock_listdir(dir_path):
+ return ['home.mount', 'var.mount', 'not-a-mount.service']
+
+ def mock_delete_file(file_path):
+ assert file_path in expected_changes
+ expected_changes[file_path]['deleted'] = True
+
+ def mock_prefix(unit_file_path, new_unit_destination):
+ assert expected_changes[unit_file_path]['new_unit_destination'] == new_unit_destination
+
+ monkeypatch.setattr('os.listdir', mock_listdir)
+ monkeypatch.setattr(mount_unit_generator, '_delete_file', mock_delete_file)
+ monkeypatch.setattr(mount_unit_generator, '_prefix_mount_unit_with_sysroot', mock_prefix)
+
+ mount_unit_generator.prefix_all_mount_units_with_sysroot('/test/dir')
+
+ for original_mount_unit_location in expected_changes:
+ should_be_deleted = expected_changes[original_mount_unit_location]['should_be_deleted']
+ was_deleted = expected_changes[original_mount_unit_location]['deleted']
+ assert should_be_deleted == was_deleted
+
+
+@pytest.mark.parametrize('dirname', (
+ 'local-fs.target.requires',
+ 'local-fs.target.wants',
+ 'local-fs-pre.target.requires',
+ 'local-fs-pre.target.wants',
+ 'remote-fs.target.requires',
+ 'remote-fs.target.wants',
+ 'remote-fs-pre.target.requires',
+ 'remote-fs-pre.target.wants',
+))
+def test_fix_symlinks_in_dir(monkeypatch, dirname):
+ """Test fixing local-fs.target.requires symlinks."""
+
+ DIR_PATH = os.path.join('/test/dir/', dirname)
+
+ def mock_rmtree(dir_path):
+ assert dir_path == DIR_PATH
+
+ def mock_mkdir(dir_path):
+ assert dir_path == DIR_PATH
+
+ def mock_listdir(dir_path):
+ return ['sysroot-home.mount', 'sysroot-var.mount', 'not-a-mount.service']
+
+ def mock_os_path_exist(dir_path):
+ assert dir_path == DIR_PATH
+ return dir_path == DIR_PATH
+
+ expected_calls = [
+ ['ln', '-s', '../sysroot-home.mount', os.path.join(DIR_PATH, 'sysroot-home.mount')],
+ ['ln', '-s', '../sysroot-var.mount', os.path.join(DIR_PATH, 'sysroot-var.mount')]
+ ]
+ call_count = 0
+
+ def mock_run(command):
+ nonlocal call_count
+ assert command in expected_calls
+ call_count += 1
+ return {
+ "stdout": "",
+ "stderr": "",
+ "exit_code": 0,
+ }
+
+ monkeypatch.setattr('shutil.rmtree', mock_rmtree)
+ monkeypatch.setattr('os.mkdir', mock_mkdir)
+ monkeypatch.setattr('os.listdir', mock_listdir)
+ monkeypatch.setattr('os.path.exists', mock_os_path_exist)
+ monkeypatch.setattr(mount_unit_generator, 'run', mock_run)
+
+ mount_unit_generator._fix_symlinks_in_dir('/test/dir', dirname)
+
+
+# Test the copy_units_into_system_location function
+def test_copy_units_mixed_content(monkeypatch):
+ """Test copying units with mixed files and directories."""
+
+ def mock_walk(dir_path):
+ tuples_to_yield = [
+ ('/source/dir', ['local-fs.target.requires'], ['unit1.mount', 'unit2.mount']),
+ ('/source/dir/local-fs.target.requires', [], ['unit1.mount', 'unit2.mount']),
+ ]
+ for i in tuples_to_yield:
+ yield i
+
+ def mock_isdir(path):
+ return 'local-fs.target.requires' in path
+
+ def _make_couple(sub_path):
+ return (
+ os.path.join('/source/dir/', sub_path),
+ os.path.join('/container/usr/lib/systemd/system/', sub_path)
+ )
+
+ def mock_copy2(src, dst, follow_symlinks=True):
+ valid_combinations = [
+ _make_couple('unit1.mount'),
+ _make_couple('unit2.mount'),
+ _make_couple('local-fs.target.requires/unit1.mount'),
+ _make_couple('local-fs.target.requires/unit2.mount'),
+ ]
+ assert not follow_symlinks
+ assert (src, dst) in valid_combinations
+
+ def mock_islink(file_path):
+ return file_path == '/container/usr/lib/systemd/system/local-fs.target.requires/unit2.mount'
+
+ class MockedDeleteFile:
+ def __init__(self):
+ self.removal_called = False
+
+ def __call__(self, file_path):
+ assert file_path == '/container/usr/lib/systemd/system/local-fs.target.requires/unit2.mount'
+ self.removal_called = True
+
+ def mock_makedirs(dst_dir, mode=0o777, exist_ok=False):
+ assert exist_ok
+ assert mode == 0o755
+
+ allowed_paths = [
+ '/container/usr/lib/systemd/system',
+ '/container/usr/lib/systemd/system/local-fs.target.requires'
+ ]
+ assert dst_dir.rstrip('/') in allowed_paths
+
+ monkeypatch.setattr(os, 'walk', mock_walk)
+ monkeypatch.setattr(os, 'makedirs', mock_makedirs)
+ monkeypatch.setattr(os.path, 'isdir', mock_isdir)
+ monkeypatch.setattr(os.path, 'islink', mock_islink)
+ monkeypatch.setattr(mount_unit_generator, '_delete_file', MockedDeleteFile())
+ monkeypatch.setattr(shutil, 'copy2', mock_copy2)
+
+ class MockedContainerContext:
+ def __init__(self):
+ self.base_dir = '/container'
+
+ def full_path(self, path):
+ return os.path.join('/container', path.lstrip('/'))
+
+ mock_container = MockedContainerContext()
+
+ files = mount_unit_generator.copy_units_into_system_location(
+ mock_container, '/source/dir'
+ )
+
+ expected_files = [
+ '/usr/lib/systemd/system/unit1.mount',
+ '/usr/lib/systemd/system/unit2.mount',
+ '/usr/lib/systemd/system/local-fs.target.requires/unit1.mount',
+ '/usr/lib/systemd/system/local-fs.target.requires/unit2.mount',
+ ]
+ assert sorted(files) == sorted(expected_files)
+ assert mount_unit_generator._delete_file.removal_called
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py
index 32e4527b..1e595e9a 100644 index 32e4527b..1e595e9a 100644
--- a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py --- a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py

View File

@ -53,7 +53,7 @@ py2_byte_compile "%1" "%2"}
Epoch: 1 Epoch: 1
Name: leapp-repository Name: leapp-repository
Version: 0.23.0 Version: 0.23.0
Release: 1%{?dist}.elevate.1 Release: 1%{?dist}.elevate.2
Summary: Repositories for leapp Summary: Repositories for leapp
License: ASL 2.0 License: ASL 2.0
@ -350,6 +350,9 @@ fi
%changelog %changelog
* Tue Sep 30 2025 Yuriy Kohut <ykohut@almalinux.org> - 0.23.0-1.elevate.2
- ELevate vendors support for upstream 0.23.0-1 version (47fce173e75408d9a7a26225d389161caf72e244)
* Fri Sep 12 2025 Yuriy Kohut <ykohut@almalinux.org> - 0.23.0-1.elevate.1 * Fri Sep 12 2025 Yuriy Kohut <ykohut@almalinux.org> - 0.23.0-1.elevate.1
- ELevate vendors support for upstream 0.23.0-1 version (dcf53c28ea9c3fdd03277abcdeb1d124660f7f8e) - ELevate vendors support for upstream 0.23.0-1 version (dcf53c28ea9c3fdd03277abcdeb1d124660f7f8e)