From 142f3d1b4de61613a9362ccfbb2305919f923c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Prchl=C3=ADk?= Date: Tue, 7 May 2024 22:28:25 +0200 Subject: [PATCH] Fix installation of packages from files (#2914) Fixes https://github.com/teemtee/tmt/issues/2911 --- tests/prepare/install/data/downloaded.fmf | 6 + tests/prepare/install/test.sh | 43 +++++++ tests/unit/test_package_managers.py | 135 ++++++++++++++++++++++ tmt/steps/prepare/install.py | 18 ++- 4 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 tests/prepare/install/data/downloaded.fmf diff --git a/tests/prepare/install/data/downloaded.fmf b/tests/prepare/install/data/downloaded.fmf new file mode 100644 index 0000000000..d2460a84d9 --- /dev/null +++ b/tests/prepare/install/data/downloaded.fmf @@ -0,0 +1,6 @@ +summary: Install downloaded packages +prepare: + - how: install + package: + - tree.rpm + - diffutils.rpm diff --git a/tests/prepare/install/test.sh b/tests/prepare/install/test.sh index 7108952163..8ae6e4ebfb 100755 --- a/tests/prepare/install/test.sh +++ b/tests/prepare/install/test.sh @@ -90,6 +90,25 @@ function is_ubi () { [[ "$1" =~ ^.*ubi.* ]] && return 0 || return 1 } +function fetch_downloaded_packages () { + if [ ! -e $package_cache/tree.rpm ]; then + # For some reason, this command will get stuck in rlRun... + container_id="$(podman run -d $1 sleep 3600)" + + rlRun "podman exec $container_id bash -c \"set -x; \ + dnf install -y 'dnf-command(download)' \ + && dnf download --destdir /tmp tree diffutils \ + && mv /tmp/tree*.rpm /tmp/tree.rpm \ + && mv /tmp/diffutils*.rpm /tmp/diffutils.rpm\"" + rlRun "podman cp $container_id:/tmp/tree.rpm $package_cache/" + rlRun "podman cp $container_id:/tmp/diffutils.rpm $package_cache/" + rlRun "podman kill $container_id" + rlRun "podman rm $container_id" + fi + + rlRun "cp $package_cache/tree.rpm ./" + rlRun "cp $package_cache/diffutils.rpm ./" +} rlJournalStart rlPhaseStartSetup @@ -107,6 +126,7 @@ rlJournalStart rlRun "IMAGES=" fi + rlRun "package_cache=\$(mktemp -d)" 0 "Create cache directory for downloaded packages" rlRun "run=\$(mktemp -d)" 0 "Create run directory" rlRun "pushd data" @@ -205,6 +225,28 @@ rlJournalStart rlPhaseEnd fi + if rlIsFedora 39 && is_fedora_39 "$image"; then + rlPhaseStartTest "$phase_prefix Install downloaded packages (plan)" + fetch_downloaded_packages "$image" + + rlRun -s "$tmt plan --name /downloaded" + + rlAssertGrep "package manager: $package_manager" $rlRun_LOG + + rlAssertGrep "summary: 2 preparations applied" $rlRun_LOG + rlPhaseEnd + + rlPhaseStartTest "$phase_prefix Install downloaded packages (CLI)" + fetch_downloaded_packages "$image" + + rlRun -s "$tmt prepare --insert --how install --package tree*.rpm --package diffutils*.rpm plan --name /empty" + + rlAssertGrep "package manager: $package_manager" $rlRun_LOG + + rlAssertGrep "summary: 2 preparations applied" $rlRun_LOG + rlPhaseEnd + fi + rlPhaseStartTest "$phase_prefix Install existing and invalid packages (plan)" rlRun -s "$tmt plan --name /missing" 2 @@ -306,6 +348,7 @@ rlJournalStart rlPhaseStartCleanup rlRun "popd" + rlRun "rm -r $package_cache" 0 "Remove package cache directory" rlRun "rm -r $run" 0 "Remove run directory" rlPhaseEnd rlJournalEnd diff --git a/tests/unit/test_package_managers.py b/tests/unit/test_package_managers.py index e5348a115e..cb9ab7ce7e 100644 --- a/tests/unit/test_package_managers.py +++ b/tests/unit/test_package_managers.py @@ -23,6 +23,7 @@ Package, PackageManager, PackageManagerClass, + PackagePath, ) from tmt.steps.provision.podman import GuestContainer, PodmanGuestData from tmt.utils import ShellScript @@ -1351,3 +1352,137 @@ def test_install_multiple( if expected_stderr: assert output.stderr is not None assert expected_stderr in output.stderr + + +def _parametrize_test_install_downloaded() -> \ + Iterator[tuple[Container, PackageManagerClass, str, Optional[str], Optional[str]]]: + + for container, package_manager_class in CONTAINER_BASE_MATRIX: + if package_manager_class is tmt.package_managers.dnf.Yum: + if 'centos:7' in container.url: + yield pytest.param( + container, + package_manager_class, + r"yum install -y --skip-broken /tmp/tree.rpm /tmp/diffutils.rpm \|\| /bin/true", # noqa: E501 + 'Complete!', + None, + marks=pytest.mark.skip(reason="CentOS 7 does not support 'download' command") + ) + + else: + yield container, \ + package_manager_class, \ + r"yum install -y --skip-broken /tmp/tree.rpm /tmp/diffutils.rpm \|\| /bin/true", \ + 'Complete!', \ + None # noqa: E501 + + elif package_manager_class is tmt.package_managers.dnf.Dnf: + yield container, \ + package_manager_class, \ + r"dnf install -y /tmp/tree.rpm /tmp/diffutils.rpm", \ + 'Complete!', \ + None + + elif package_manager_class is tmt.package_managers.dnf.Dnf5: + yield container, \ + package_manager_class, \ + r"dnf5 install -y /tmp/tree.rpm /tmp/diffutils.rpm", \ + None, \ + None + + elif package_manager_class is tmt.package_managers.rpm_ostree.RpmOstree: + yield container, \ + package_manager_class, \ + r"rpm-ostree install --apply-live --idempotent --allow-inactive /tmp/tree.rpm /tmp/diffutils.rpm", \ + 'Installing: tree', \ + None # noqa: E501 + + elif package_manager_class is tmt.package_managers.apt.Apt: + yield pytest.param( + container, + package_manager_class, + r"export DEBIAN_FRONTEND=noninteractive; dpkg-query --show tree diffutils \|\| apt install -y tree diffutils", # noqa: E501 + 'Setting up tree', + None, + marks=pytest.mark.skip(reason="not supported yet") + ) + + elif package_manager_class is tmt.package_managers.apk.Apk: + yield pytest.param( + container, + package_manager_class, + r"apk info -e tree diffutils \|\| apk add tree diffutils", + 'Installing tree', + None, + marks=pytest.mark.skip(reason="not supported yet") + ) + + else: + pytest.fail(f"Unhandled package manager class '{package_manager_class}'.") + + +@pytest.mark.containers() +@pytest.mark.parametrize(('container_per_test', + 'package_manager_class', + 'expected_command', + 'expected_stdout', + 'expected_stderr'), + list(_parametrize_test_install_downloaded()), + indirect=["container_per_test"], + ids=CONTAINER_MATRIX_IDS) +def test_install_downloaded( + container_per_test: ContainerData, + guest_per_test: GuestContainer, + package_manager_class: PackageManagerClass, + expected_command: str, + expected_stdout: Optional[str], + expected_stderr: Optional[str], + root_logger: tmt.log.Logger, + caplog: _pytest.logging.LogCaptureFixture) -> None: + package_manager = create_package_manager( + container_per_test, + guest_per_test, + package_manager_class, + root_logger) + + # TODO: move to a fixture + guest_per_test.execute(ShellScript( + """ + (yum download --destdir /tmp tree diffutils \ + || (dnf install -y 'dnf-command(download)' && dnf download --destdir /tmp tree diffutils) \ + || (dnf5 install -y 'dnf-command(download)' && dnf5 download --destdir /tmp tree diffutils)) \ + && mv /tmp/tree*.rpm /tmp/tree.rpm && mv /tmp/diffutils*.rpm /tmp/diffutils.rpm + """)) # noqa: E501 + + # TODO: yum and downloaded packages results in post-install `rpm -q` + # check to make sure packages were indeed installed - but that + # breaks because that test uses original package paths, not package + # names, and fails. Disable this check for now, but it's a sloppy + # "solution". + if package_manager_class is tmt.package_managers.dnf.Yum: + output = package_manager.install( + PackagePath('/tmp/tree.rpm'), + PackagePath('/tmp/diffutils.rpm'), + options=Options( + check_first=False, + skip_missing=True + )) + + else: + output = package_manager.install( + PackagePath('/tmp/tree.rpm'), + PackagePath('/tmp/diffutils.rpm'), + options=Options( + check_first=False + )) + + assert_log(caplog, message=MATCH( + rf"Run command: podman exec .+? /bin/bash -c '{expected_command}'")) + + if expected_stdout: + assert output.stdout is not None + assert expected_stdout in output.stdout + + if expected_stderr: + assert output.stderr is not None + assert expected_stderr in output.stderr diff --git a/tmt/steps/prepare/install.py b/tmt/steps/prepare/install.py index d22ebf05c1..d5e4eece07 100644 --- a/tmt/steps/prepare/install.py +++ b/tmt/steps/prepare/install.py @@ -267,7 +267,8 @@ def install_local(self) -> None: *filelist, options=Options( excluded_packages=self.exclude, - skip_missing=self.skip_missing + skip_missing=self.skip_missing, + check_first=False ) ) @@ -275,7 +276,8 @@ def install_local(self) -> None: *filelist, options=Options( excluded_packages=self.exclude, - skip_missing=self.skip_missing + skip_missing=self.skip_missing, + check_first=False ) ) @@ -385,7 +387,11 @@ def install_local(self) -> None: for package in self.local_packages: try: self.guest.package_manager.install( - PackagePath(self.package_directory / package.name)) + PackagePath(self.package_directory / package.name), + options=Options( + check_first=False + ) + ) local_packages_installed.append(package) except tmt.utils.RunError as error: self.warn(f"Local package '{package}' not installed: {error.stderr}") @@ -426,7 +432,8 @@ def install_local(self) -> None: *self.list_installables('local packages', *filelist), options=Options( excluded_packages=self.exclude, - skip_missing=self.skip_missing + skip_missing=self.skip_missing, + check_first=False ) ) @@ -488,7 +495,8 @@ def install_local(self) -> None: options=Options( excluded_packages=self.exclude, skip_missing=self.skip_missing, - allow_untrusted=True + allow_untrusted=True, + check_first=False ) )