Skip to content

Commit

Permalink
Support Fedora Image Mode
Browse files Browse the repository at this point in the history
Signed-off-by: Miroslav Vadkerti <[email protected]>
  • Loading branch information
thrix committed Oct 1, 2024
1 parent cddc98e commit 71c72d1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 31 deletions.
91 changes: 77 additions & 14 deletions tmt/steps/execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import signal as _signal
import subprocess
import tempfile
import threading
from contextlib import suppress
from dataclasses import dataclass
Expand All @@ -27,13 +28,15 @@
from tmt.steps.discover import Discover, DiscoverPlugin, DiscoverStepData
from tmt.steps.provision import Guest
from tmt.utils import (
Command,
Path,
ShellScript,
Stopwatch,
field,
format_duration,
format_timestamp,
)
from tmt.utils.templates import render_template_file

if TYPE_CHECKING:
import tmt.cli
Expand All @@ -58,6 +61,9 @@
# Scripts source directory
SCRIPTS_SRC_DIR = tmt.utils.resource_files('steps/execute/scripts')

#: The default scripts destination directory
SCRIPTS_DEST_DIR = Path("/var/tmp/tmt/bin") # noqa: S108 insecure usage of temporary dir


@dataclass
class Script:
Expand All @@ -75,12 +81,36 @@ class ScriptCreatingFile(Script):
created_file: str


@dataclass
class ScriptTemplate(Script):
"""
Represents a Jinja2 templated script.
The source filename must have a ``.j2`` suffix.
"""

context: dict[str, str]


def effective_scripts_dest_dir() -> Path:
"""
Find out what the actual scripts destination directory is.
If ``TMT_SCRIPTS_DEST_DIR`` variable is set, it is used as the scripts destination
directory. Otherwise, the default of :py:data:`SCRIPTS_DEST_DIR` is used.
"""

if 'TMT_SCRIPTS_DEST_DIR' in os.environ:
return Path(os.environ['TMT_SCRIPTS_DEST_DIR'])

return SCRIPTS_DEST_DIR


# Script handling reboots, in restraint compatible fashion
TMT_REBOOT_SCRIPT = ScriptCreatingFile(
path=Path("/usr/local/bin/tmt-reboot"),
path=effective_scripts_dest_dir() / 'tmt-reboot',
aliases=[
Path("/usr/local/bin/rstrnt-reboot"),
Path("/usr/local/bin/rhts-reboot")],
effective_scripts_dest_dir() / 'rstrnt-reboot',
effective_scripts_dest_dir() / 'rhts-reboot'],
related_variables=[
"TMT_REBOOT_COUNT",
"REBOOTCOUNT",
Expand All @@ -89,43 +119,54 @@ class ScriptCreatingFile(Script):
)

TMT_REBOOT_CORE_SCRIPT = Script(
path=Path("/usr/local/bin/tmt-reboot-core"),
path=effective_scripts_dest_dir() / 'tmt-reboot-core',
aliases=[],
related_variables=[])

# Script handling result reporting, in restraint compatible fashion
TMT_REPORT_RESULT_SCRIPT = ScriptCreatingFile(
path=Path("/usr/local/bin/tmt-report-result"),
path=effective_scripts_dest_dir() / 'tmt-report-result',
aliases=[
Path("/usr/local/bin/rstrnt-report-result"),
Path("/usr/local/bin/rhts-report-result")],
effective_scripts_dest_dir() / 'rstrnt-report-result',
effective_scripts_dest_dir() / 'rhts-report-result'],
related_variables=[],
created_file="tmt-report-results.yaml"
)

# Script for archiving a file, usable for BEAKERLIB_COMMAND_SUBMIT_LOG
TMT_FILE_SUBMIT_SCRIPT = Script(
path=Path("/usr/local/bin/tmt-file-submit"),
path=effective_scripts_dest_dir() / 'tmt-file-submit',
aliases=[
Path("/usr/local/bin/rstrnt-report-log"),
Path("/usr/local/bin/rhts-submit-log"),
Path("/usr/local/bin/rhts_submit_log")],
effective_scripts_dest_dir() / 'rstrnt-report-log',
effective_scripts_dest_dir() / 'rhts-submit-log',
effective_scripts_dest_dir() / 'rhts_submit_log'],
related_variables=[]
)

# Script handling text execution abortion, in restraint compatible fashion
TMT_ABORT_SCRIPT = ScriptCreatingFile(
path=Path("/usr/local/bin/tmt-abort"),
path=effective_scripts_dest_dir() / 'tmt-abort',
aliases=[
Path("/usr/local/bin/rstrnt-abort"),
Path("/usr/local/bin/rhts-abort")],
effective_scripts_dest_dir() / 'rstrnt-abort',
effective_scripts_dest_dir() / 'rhts-abort'],
related_variables=[],
created_file="abort"
)

# Profile script for adding SCRIPTS_DEST_DIR to executable pats system-wide
TMT_ETC_PROFILE_D = ScriptTemplate(
path=Path("/etc/profile.d/tmt.sh"),
aliases=[],
related_variables=[],
context={
'scripts_dest_dir': str(effective_scripts_dest_dir())
})


# List of all available scripts
SCRIPTS = (
TMT_ABORT_SCRIPT,
TMT_ETC_PROFILE_D,
TMT_FILE_SUBMIT_SCRIPT,
TMT_REBOOT_SCRIPT,
TMT_REBOOT_CORE_SCRIPT,
Expand Down Expand Up @@ -595,19 +636,41 @@ def prepare_tests(self, guest: Guest, logger: tmt.log.Logger) -> list[TestInvoca

return invocations

def _render_script_template(self, source: Path, context: dict[str, str]) -> Path:
""" Render script template with given context """

with tempfile.NamedTemporaryFile(mode='w', delete=False) as rendered_script:
rendered_script.write(render_template_file(source, None, **context))

return Path(rendered_script.name)

def prepare_scripts(self, guest: "tmt.steps.provision.Guest") -> None:
""" Prepare additional scripts for testing """
# Create scripts directory
guest.execute(Command("mkdir", "-p", str(SCRIPTS_DEST_DIR)))

# Install all scripts on guest
for script in self.scripts:
source = SCRIPTS_SRC_DIR / script.path.name

# Render script template
if isinstance(script, ScriptTemplate):
source = self._render_script_template(
SCRIPTS_SRC_DIR / f"{script.path.name}.j2",
context=script.context
)

for dest in [script.path, *script.aliases]:
guest.push(
source=source,
destination=dest,
options=["-p", "--chmod=755"],
superuser=guest.facts.is_superuser is not True)

# Remove script template source
if isinstance(script, ScriptTemplate):
os.unlink(source)

def _tmt_report_results_filepath(self, invocation: TestInvocation) -> Path:
""" Create path to test's ``tmt-report-result`` file """

Expand Down
4 changes: 4 additions & 0 deletions tmt/steps/execute/scripts/tmt.sh.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# shellcheck shell=bash

# tmt provides executable scripts under this path
export PATH={{ scripts_dest_dir }}:$PATH

Check warning

Code scanning / shellcheck

This assignment is used again in this 'export', but won't have taken effect. Use two 'export's. Warning

This assignment is used again in this 'export', but won't have taken effect. Use two 'export's.

Check warning

Code scanning / shellcheck

This { is literal. Check expression (missing ;/\n?) or quote it. Warning

This { is literal. Check expression (missing ;/\n?) or quote it.

Check warning

Code scanning / shellcheck

This { is literal. Check expression (missing ;/\n?) or quote it. Warning

This { is literal. Check expression (missing ;/\n?) or quote it.

Check warning

Code scanning / shellcheck

This } is literal. Check expression (missing ;/\n?) or quote it. Warning

This } is literal. Check expression (missing ;/\n?) or quote it.

Check warning

Code scanning / shellcheck

This } is literal. Check expression (missing ;/\n?) or quote it. Warning

This } is literal. Check expression (missing ;/\n?) or quote it.
18 changes: 1 addition & 17 deletions tmt/steps/provision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,23 +1303,7 @@ def _check_rsync(self) -> CheckRsyncOutcome:
except tmt.utils.RunError:
pass

# Install under '/root/pkg' for read-only distros
# (for now the check is based on 'rpm-ostree' presence)
# FIXME: Find a better way how to detect read-only distros
# self.debug("Check for a read-only distro.")
if self.facts.package_manager == 'rpm-ostree':
self.package_manager.install(
Package('rsync'),
options=tmt.package_managers.Options(
install_root=Path('/root/pkg'),
release_version='/'
)
)

self.execute(Command('ln', '-sf', '/root/pkg/bin/rsync', '/usr/local/bin/rsync'))

else:
self.package_manager.install(Package('rsync'))
self.package_manager.install(Package('rsync'))

return CheckRsyncOutcome.INSTALLED

Expand Down

0 comments on commit 71c72d1

Please sign in to comment.