diff --git a/docs/releases.rst b/docs/releases.rst index 6de99ee0cd..11c3b0a2e2 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -32,6 +32,9 @@ execution. Use ``tmt run discover -vvv`` to see the details. ``tmt try`` now supports :ref:`/stories/cli/try/option/epel` option backed by :ref:`prepare/feature` plugin. +``tmt try`` now supports :ref:`/stories/cli/try/option/install` option +backed by :ref:`prepare/feature` plugin. + tmt-1.36.1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/stories/cli/try.fmf b/stories/cli/try.fmf index 6668a92578..c0e1feb206 100644 --- a/stories/cli/try.fmf +++ b/stories/cli/try.fmf @@ -146,3 +146,18 @@ link: link: - implemented-by: /tmt/trying.py - implemented-by: /tmt/steps/prepare/feature.py + + /install: + story: + As a user I want an easy way to install local rpm package on the guest. + description: | + Install local rpm package on the guest. + + .. versionadded:: 1.37 + + example: + - tmt try fedora@virtual --install tree + + link: + - implemented-by: /tmt/trying.py + - implemented-by: /tmt/steps/prepare/install.py diff --git a/tests/try/basic/data/install.exp b/tests/try/basic/data/install.exp new file mode 100755 index 0000000000..4fcb7a00ea --- /dev/null +++ b/tests/try/basic/data/install.exp @@ -0,0 +1,8 @@ +#!/usr/bin/expect -f +# Try install + +set timeout 180 +spawn tmt try fedora@container --install tree -p /plans/basic +expect "What do we do next?" +send -- "q\r" +expect eof diff --git a/tests/try/basic/test.sh b/tests/try/basic/test.sh index 0897bd09d7..6adc35c87d 100755 --- a/tests/try/basic/test.sh +++ b/tests/try/basic/test.sh @@ -34,6 +34,14 @@ rlJournalStart rlAssertGrep "Run .* successfully finished. Bye for now!" $rlRun_LOG rlPhaseEnd + rlPhaseStartTest "Install" + rlRun -s "./install.exp" + rlAssertGrep "Let's try.*/plans/basic" $rlRun_LOG + rlAssertGrep "cmd: rpm -q --whatprovides tree || dnf install -y tree" $rlRun_LOG + rlAssertGrep "out: Installed:" $rlRun_LOG + rlAssertGrep "Run .* successfully finished. Bye for now!" $rlRun_LOG + rlPhaseEnd + rlPhaseStartTest "Verbose Output" rlRun -s "TMT_CONFIG_DIR=$config ./verbose.exp" rlAssertGrep "custom-prepare" $rlRun_LOG diff --git a/tmt/cli.py b/tmt/cli.py index 7f72fd21bf..b78f79786f 100644 --- a/tmt/cli.py +++ b/tmt/cli.py @@ -1752,6 +1752,9 @@ def init( @option( "--epel", is_flag=True, default=False, help="Enable epel repository.") +@option( + "--install", default=[], metavar="PACKAGE", multiple=True, + help="Install local rpm package on the guest.") @option( "-a", "--ask", is_flag=True, default=False, help="Just provision the guest and ask what to do next.") diff --git a/tmt/steps/execute/upgrade.py b/tmt/steps/execute/upgrade.py index 801ad11957..3e421cc655 100644 --- a/tmt/steps/execute/upgrade.py +++ b/tmt/steps/execute/upgrade.py @@ -16,7 +16,7 @@ from tmt.steps.execute import ExecutePlugin from tmt.steps.execute.internal import ExecuteInternal, ExecuteInternalData from tmt.steps.prepare import PreparePlugin -from tmt.steps.prepare.install import _RawPrepareInstallStepData +from tmt.steps.prepare.install import PrepareInstallData from tmt.utils import Environment, EnvVarValue, Path, field STATUS_VARIABLE = 'IN_PLACE_UPGRADE' @@ -267,17 +267,14 @@ def _install_dependencies( recommends: bool = False) -> None: """ Install packages required/recommended for upgrade """ phase_name = 'recommended' if recommends else 'required' - data: _RawPrepareInstallStepData = { - 'how': 'install', - 'name': f'{phase_name}-packages-upgrade', - 'summary': f'Install packages {phase_name} by the upgrade', - 'package': [ - dependency.to_spec() - for dependency in tmt.utils.uniq(dependencies) - ], - 'missing': 'skip' if recommends else 'fail' - } - PreparePlugin.delegate(self.step, raw_data=data).go( # type:ignore[attr-defined] + data = PrepareInstallData( + how='install', + name=f'{phase_name}-packages-upgrade', + summary=f'Install packages {phase_name} by the upgrade', + package=tmt.utils.uniq(dependencies), + missing='skip' if recommends else 'fail') + + PreparePlugin.delegate(self.step, data=data).go( # type:ignore[attr-defined] guest=guest, logger=self._logger) def _prepare_remote_discover_data(self, plan: tmt.base.Plan) -> tmt.steps._RawStepData: diff --git a/tmt/steps/prepare/distgit.py b/tmt/steps/prepare/distgit.py index 4c9881b406..a4a5e76f24 100644 --- a/tmt/steps/prepare/distgit.py +++ b/tmt/steps/prepare/distgit.py @@ -10,7 +10,7 @@ from tmt.package_managers import Package from tmt.result import PhaseResult from tmt.steps.prepare import PreparePlugin -from tmt.steps.prepare.install import _RawPrepareInstallStepData +from tmt.steps.prepare.install import PrepareInstallData from tmt.steps.provision import Guest from tmt.utils import Command, Path, ShellScript, field, uniq @@ -55,30 +55,30 @@ def insert_to_prepare_step( prepare_step = discover_plugin.step.plan.prepare where = cast(tmt.steps.discover.DiscoverStepData, discover_plugin.data).where # Future install require - data_require: _RawPrepareInstallStepData = { - 'how': 'install', - 'name': 'requires (dist-git)', - 'summary': 'Install required packages of tests detected by dist-git', - 'order': tmt.utils.DEFAULT_PLUGIN_ORDER_REQUIRES, - 'where': where, - 'package': []} + data_require = PrepareInstallData( + how='install', + name='requires (dist-git)', + summary='Install required packages of tests detected by dist-git', + order=tmt.utils.DEFAULT_PLUGIN_ORDER_REQUIRES, + where=where, + package=[]) future_requires: PreparePlugin[Any] = cast( - PreparePlugin[Any], PreparePlugin.delegate( - prepare_step, raw_data=data_require)) + PreparePlugin[Any], + PreparePlugin.delegate(prepare_step, data=data_require)) prepare_step._phases.append(future_requires) # Future install recommend - data_recommend: _RawPrepareInstallStepData = { - 'how': 'install', - 'name': 'recommends (dist-git)', - 'summary': 'Install recommended packages of tests detected by dist-git', - 'order': tmt.utils.DEFAULT_PLUGIN_ORDER_RECOMMENDS, - 'where': where, - 'package': [], - 'missing': 'skip'} + data_recommend = PrepareInstallData( + how='install', + name='recommends (dist-git)', + summary='Install recommended packages of tests detected by dist-git', + order=tmt.utils.DEFAULT_PLUGIN_ORDER_RECOMMENDS, + where=where, + package=[], + missing='skip') future_recommends: PreparePlugin[Any] = cast( - PreparePlugin[Any], PreparePlugin.delegate( - prepare_step, raw_data=data_recommend)) + PreparePlugin[Any], + PreparePlugin.delegate(prepare_step, data=data_recommend)) prepare_step._phases.append(future_recommends) prepare_step._phases.append( diff --git a/tmt/steps/prepare/feature.py b/tmt/steps/prepare/feature.py index 63e7041a28..ee0310aabd 100644 --- a/tmt/steps/prepare/feature.py +++ b/tmt/steps/prepare/feature.py @@ -78,10 +78,6 @@ def disable(self) -> None: } -class _RawPrepareFeatureStepData(tmt.steps.prepare._RawPrepareStepData, total=False): - epel: str - - @dataclasses.dataclass class PrepareFeatureData(tmt.steps.prepare.PrepareStepData): epel: Optional[str] = field( diff --git a/tmt/steps/prepare/install.py b/tmt/steps/prepare/install.py index 062842442a..8b99bc9623 100644 --- a/tmt/steps/prepare/install.py +++ b/tmt/steps/prepare/install.py @@ -526,14 +526,6 @@ def install_debuginfo(self) -> None: 'installing debuginfo packages.') -class _RawPrepareInstallStepData(tmt.steps.prepare._RawPrepareStepData, total=False): - package: Union[str, list[str]] - directory: Union[str, list[str]] - copr: Union[str, list[str]] - exclude: Union[str, list[str]] - missing: str - - @dataclasses.dataclass class PrepareInstallData(tmt.steps.prepare.PrepareStepData): package: list[tmt.base.DependencySimple] = field( diff --git a/tmt/trying.py b/tmt/trying.py index 030fb33801..e0f7f9b484 100644 --- a/tmt/trying.py +++ b/tmt/trying.py @@ -21,7 +21,6 @@ from tmt import Plan from tmt.base import RunData from tmt.steps.prepare import PreparePlugin -from tmt.steps.prepare.feature import _RawPrepareFeatureStepData from tmt.utils import MetadataError, Path USER_PLAN_NAME = "/user/plan" @@ -105,7 +104,7 @@ def __init__( self.tests: list[tmt.Test] = [] self.plans: list[Plan] = [] self.image_and_how = self.opt("image_and_how") - self.cli_options = ["epel"] + self.cli_options = ["epel", "install"] # Use the verbosity level 3 unless user explicitly requested # a different level on the command line @@ -416,15 +415,34 @@ def handle_epel(self, plan: Plan) -> None: """ Enable EPEL repository """ # tmt run prepare --how feature --epel enabled - data: _RawPrepareFeatureStepData = { - "name": "tmt-try-epel", - 'how': 'feature', - 'epel': "enabled", - } + from tmt.steps.prepare.feature import PrepareFeatureData + + data = PrepareFeatureData( + name="tmt-try-epel", + how='feature', + epel="enabled") phase: PreparePlugin[Any] = cast( - PreparePlugin[Any], PreparePlugin.delegate( - plan.prepare, raw_data=data)) + PreparePlugin[Any], + PreparePlugin.delegate(plan.prepare, data=data)) + + plan.prepare._phases.append(phase) + + def handle_install(self, plan: Plan) -> None: + """ Install local rpm package on the guest. """ + + # tmt run prepare --how install --package PACKAGE + from tmt.steps.prepare.install import PrepareInstallData + + data = PrepareInstallData( + name="tmt-try-install", + how='install', + package=list(self.opt("install"))) + + phase: PreparePlugin[Any] = cast( + PreparePlugin[Any], + PreparePlugin.delegate(plan.prepare, data=data)) + plan.prepare._phases.append(phase) def go(self) -> None: