diff --git a/configure.ac b/configure.ac index 7e09487129f..0019af31849 100644 --- a/configure.ac +++ b/configure.ac @@ -186,6 +186,7 @@ AC_CONFIG_FILES([Makefile pyanaconda/modules/payloads/source/url/Makefile pyanaconda/modules/runtime/Makefile pyanaconda/modules/runtime/dracut_commands/Makefile + pyanaconda/modules/runtime/scripts/Makefile pyanaconda/modules/runtime/user_interface/Makefile pyanaconda/modules/storage/Makefile pyanaconda/modules/storage/bootloader/Makefile diff --git a/pyanaconda/core/kickstart/scripts.py b/pyanaconda/core/kickstart/scripts.py new file mode 100644 index 00000000000..c39a70e22dd --- /dev/null +++ b/pyanaconda/core/kickstart/scripts.py @@ -0,0 +1,80 @@ +# +# Command utilities for working with scripts +# +# Copyright (C) 2024 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. +# +import os +import tempfile +from pyanaconda.core import util +from pyanaconda.core.path import open_with_perm + +from pyanaconda.anaconda_loggers import get_module_logger +log = get_module_logger(__name__) + +script_log = log.getChild("script") + + +__all__ = ["run_script"] + + +def run_script(script, chroot): + """ Run the kickstart script + + This will write the script to a file named /tmp/ks-script- before + execution. + Output is logged by the program logger, the path specified by --log + or to /tmp/ks-script-\\*.log + @param chroot directory path to chroot into before execution + """ + if script.inChroot: + scriptRoot = chroot + else: + scriptRoot = "/" + + (fd, path) = tempfile.mkstemp("", "ks-script-", scriptRoot + "/tmp") + + os.write(fd, script.script.encode("utf-8")) + os.close(fd) + os.chmod(path, 0o700) + + # Always log stdout/stderr from scripts. Using --log just lets you + # pick where it goes. The script will also be logged to program.log + # because of execWithRedirect. + if script.logfile: + if script.inChroot: + messages = "%s/%s" % (scriptRoot, script.logfile) + else: + messages = script.logfile + + d = os.path.dirname(messages) + if not os.path.exists(d): + os.makedirs(d) + else: + # Always log outside the chroot, we copy those logs into the + # chroot later. + messages = "/tmp/%s.log" % os.path.basename(path) + + with open_with_perm(messages, "w", 0o600) as fp: + rc = util.execWithRedirect(script.interp, ["/tmp/%s" % os.path.basename(path)], + stdout=fp, + root=scriptRoot) + + if rc != 0: + script_log.error("Error code %s running the kickstart script at line %s", + rc, script.lineno) + + return rc, messages diff --git a/pyanaconda/core/kickstart/specification.py b/pyanaconda/core/kickstart/specification.py index e261ec2a01d..78a5eba94ab 100644 --- a/pyanaconda/core/kickstart/specification.py +++ b/pyanaconda/core/kickstart/specification.py @@ -91,6 +91,8 @@ def __init__(self, specification): for name, data in specification.addons.items(): self.registerAddonData(name, data) + self.scripts = [] + def registerSectionData(self, name, data): """Register data used by a section.""" obj = data() diff --git a/pyanaconda/errors.py b/pyanaconda/errors.py index ded2804ab35..c8fda1e389a 100644 --- a/pyanaconda/errors.py +++ b/pyanaconda/errors.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - +from pyanaconda.anaconda_loggers import get_module_logger from pyanaconda.core.i18n import _, C_ from pyanaconda.flags import flags from pyanaconda.modules.common.errors.installation import BootloaderInstallationError, \ @@ -24,6 +24,7 @@ from pyanaconda.modules.common.errors.payload import SourceSetupError from pyanaconda.modules.common.errors.storage import UnusableStorageError +log = get_module_logger(__name__) class ScriptError(Exception): def __init__(self, lineno, details): diff --git a/pyanaconda/exception.py b/pyanaconda/exception.py index 87a43fc8a3c..0d19fcf7424 100644 --- a/pyanaconda/exception.py +++ b/pyanaconda/exception.py @@ -33,7 +33,6 @@ from meh.dump import ReverseExceptionDump from meh.handler import ExceptionHandler -from pyanaconda import kickstart from pyanaconda.core import util from pyanaconda.core.async_utils import run_in_loop from pyanaconda.core.configuration.anaconda import conf @@ -41,12 +40,16 @@ from pyanaconda.core.product import get_product_is_final_release from pyanaconda.errors import NonInteractiveError from pyanaconda.core.i18n import _ +from pyanaconda.modules.common.constants.objects import SCRIPTS +from pyanaconda.modules.common.constants.services import RUNTIME from pyanaconda.modules.common.errors.storage import UnusableStorageError from pyanaconda.core.threads import thread_manager +from pyanaconda.modules.common.task import sync_run_task from pyanaconda.ui.communication import hubQ from simpleline import App from simpleline.event_loop.signals import ExceptionSignal +from pykickstart.constants import KS_SCRIPT_ONERROR, KS_SCRIPT_TRACEBACK from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) @@ -236,13 +239,20 @@ def postWriteHook(self, dump_info): util.ipmi_report(IPMI_FAILED) - def _run_kickstart_scripts(self, dump_info): + def _run_kickstart_scripts(self, _dump_info): """Run the %traceback and %onerror kickstart scripts.""" - anaconda = dump_info.object + scripts_proxy = RUNTIME.get_proxy(SCRIPTS) + # OnError script call + onerror_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_ONERROR) + onerror_task_proxy = RUNTIME.get_proxy(onerror_task_path) + + # Traceback script call + traceback_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_TRACEBACK) + traceback_task_proxy = RUNTIME.get_proxy(traceback_task_path) try: - util.runOnErrorScripts(anaconda.ksdata.scripts) - kickstart.runTracebackScripts(anaconda.ksdata.scripts) + sync_run_task(onerror_task_proxy) + sync_run_task(traceback_task_proxy) # pylint: disable=bare-except # ruff: noqa: E722 except: diff --git a/pyanaconda/installation.py b/pyanaconda/installation.py index 77dcfa49d7c..6b492c5ae01 100644 --- a/pyanaconda/installation.py +++ b/pyanaconda/installation.py @@ -23,9 +23,9 @@ CATEGORY_BOOTLOADER, CATEGORY_ENVIRONMENT, CATEGORY_STORAGE, CATEGORY_SOFTWARE from pyanaconda.modules.boss.install_manager.installation_category_interface \ import CategoryReportTaskInterface -from pyanaconda.modules.common.constants.objects import BOOTLOADER, SNAPSHOT, FIREWALL +from pyanaconda.modules.common.constants.objects import BOOTLOADER, SNAPSHOT, FIREWALL, SCRIPTS from pyanaconda.modules.common.constants.services import STORAGE, USERS, SERVICES, NETWORK, \ - SECURITY, LOCALIZATION, TIMEZONE, BOSS, SUBSCRIPTION + SECURITY, LOCALIZATION, TIMEZONE, BOSS, SUBSCRIPTION, RUNTIME from pyanaconda.modules.common.task import sync_run_task, Task as InstallationTask from pyanaconda.modules.common.util import is_module_available from pyanaconda import flags @@ -35,10 +35,10 @@ from pyanaconda import network from pyanaconda.core.i18n import _ from pyanaconda.core.threads import thread_manager -from pyanaconda.kickstart import runPostScripts, runPreInstallScripts from pyanaconda.kexec import setup_kexec from pyanaconda.installation_tasks import Task, TaskQueue, DBusTask -from pykickstart.constants import SNAPSHOT_WHEN_POST_INSTALL +from pykickstart.constants import (SNAPSHOT_WHEN_POST_INSTALL, KS_SCRIPT_PREINSTALL, + KS_SCRIPT_POST) from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) @@ -286,11 +286,10 @@ def run_generate_initramfs(): _("Running post-installation scripts"), CATEGORY_SYSTEM ) - post_scripts.append(Task( - "Run post installation scripts", - runPostScripts, - (ksdata.scripts,) - )) + scripts_proxy = RUNTIME.get_proxy(SCRIPTS) + post_scripts.append_dbus_tasks(RUNTIME, [ + scripts_proxy.RunScriptsWithTask(KS_SCRIPT_POST) + ]) configuration_queue.append(post_scripts) boss_proxy = BOSS.get_proxy() @@ -376,10 +375,10 @@ def _prepare_installation(self, payload, ksdata): _("Running pre-installation scripts"), CATEGORY_ENVIRONMENT ) - pre_install_scripts.append(Task( - "Run %pre-install scripts", - runPreInstallScripts, (ksdata.scripts,) - )) + scripts_proxy = RUNTIME.get_proxy(SCRIPTS) + pre_install_scripts.append_dbus_tasks(RUNTIME, [ + scripts_proxy.RunScriptsWithTask(KS_SCRIPT_PREINSTALL) + ]) installation_queue.append(pre_install_scripts) # Do various pre-installation tasks diff --git a/pyanaconda/installation_tasks.py b/pyanaconda/installation_tasks.py index affb3e3ca94..e2fa71f01f8 100644 --- a/pyanaconda/installation_tasks.py +++ b/pyanaconda/installation_tasks.py @@ -17,11 +17,17 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # +import sys import time from dasbus.error import DBusError + +from pyanaconda.core import util +from pyanaconda.core.constants import IPMI_ABORTED from pyanaconda.core.signal import Signal from pyanaconda.errors import errorHandler, ERROR_RAISE +from pyanaconda.modules.common.errors.runtime import ScriptError +from pyanaconda.flags import flags from pyanaconda.modules.common.task import sync_run_task from pyanaconda.anaconda_loggers import get_module_logger @@ -295,8 +301,14 @@ def _run(self): sync_run_task(self._task_proxy) except DBusError as e: # Handle a remote error. - if errorHandler.cb(e) == ERROR_RAISE: - raise + if isinstance(e, ScriptError): + flags.ksprompt = True + errorHandler.cb(e) + util.ipmi_report(IPMI_ABORTED) + sys.exit(0) + else: + if errorHandler.cb(e) == ERROR_RAISE: + raise finally: # Disconnect from the signal. self._task_proxy.ProgressChanged.disconnect() diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index 06848739335..a39c1f63e95 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -19,10 +19,7 @@ # import glob -import os -import os.path import sys -import tempfile import time import warnings @@ -30,19 +27,18 @@ from pyanaconda.anaconda_loggers import get_module_logger, get_stdout_logger from pyanaconda.core import util -from pyanaconda.core.path import open_with_perm -from pyanaconda.core.configuration.anaconda import conf +from pyanaconda.core.kickstart.scripts import run_script from pyanaconda.core.kickstart import VERSION, commands as COMMANDS from pyanaconda.core.kickstart.specification import KickstartSpecification from pyanaconda.core.constants import IPMI_ABORTED -from pyanaconda.errors import ScriptError, errorHandler -from pyanaconda.flags import flags from pyanaconda.core.i18n import _ +from pyanaconda.errors import errorHandler, ScriptError +from pyanaconda.flags import flags from pyanaconda.modules.common.constants.services import BOSS from pyanaconda.modules.common.structures.kickstart import KickstartReport from pykickstart.base import KickstartCommand, RemovedCommand -from pykickstart.constants import KS_SCRIPT_POST, KS_SCRIPT_PRE, KS_SCRIPT_TRACEBACK, KS_SCRIPT_PREINSTALL +from pykickstart.constants import KS_SCRIPT_PRE from pykickstart.errors import KickstartError, KickstartParseWarning from pykickstart.ko import KickstartObject from pykickstart.parser import KickstartParser @@ -72,63 +68,22 @@ def check_kickstart_error(): class AnacondaKSScript(KSScript): - """ Execute a kickstart script - - This will write the script to a file named /tmp/ks-script- before - execution. - Output is logged by the program logger, the path specified by --log - or to /tmp/ks-script-\\*.log - """ def run(self, chroot): - """ Run the kickstart script - @param chroot directory path to chroot into before execution - """ - if self.inChroot: - scriptRoot = chroot - else: - scriptRoot = "/" - - (fd, path) = tempfile.mkstemp("", "ks-script-", scriptRoot + "/tmp") - - os.write(fd, self.script.encode("utf-8")) - os.close(fd) - os.chmod(path, 0o700) - - # Always log stdout/stderr from scripts. Using --log just lets you - # pick where it goes. The script will also be logged to program.log - # because of execWithRedirect. - if self.logfile: - if self.inChroot: - messages = "%s/%s" % (scriptRoot, self.logfile) - else: - messages = self.logfile - - d = os.path.dirname(messages) - if not os.path.exists(d): - os.makedirs(d) - else: - # Always log outside the chroot, we copy those logs into the - # chroot later. - messages = "/tmp/%s.log" % os.path.basename(path) - - with open_with_perm(messages, "w", 0o600) as fp: - rc = util.execWithRedirect(self.interp, ["/tmp/%s" % os.path.basename(path)], - stdout=fp, - root=scriptRoot) - - if rc != 0: - script_log.error("Error code %s running the kickstart script at line %s", rc, self.lineno) - if self.errorOnFail: - err = "" - with open(messages, "r") as fp: - err = "".join(fp.readlines()) + rc, log_file = run_script(self, chroot) + if self.errorOnFail and rc != 0: + err = "" + with open(log_file, "r") as fp: + err = "".join(fp.readlines()) + if self.type == KS_SCRIPT_PRE: # Show error dialog even for non-interactive flags.ksprompt = True errorHandler.cb(ScriptError(self.lineno, err)) util.ipmi_report(IPMI_ABORTED) sys.exit(0) + else: + return self.lineno, err class AnacondaInternalScript(AnacondaKSScript): @@ -386,19 +341,6 @@ def appendPostScripts(ksdata): ksparser = AnacondaKSParser(ksdata, scriptClass=AnacondaInternalScript) ksparser.readKickstartFromString(scripts, reset=False) - -def runPostScripts(scripts): - postScripts = [s for s in scripts if s.type == KS_SCRIPT_POST] - - if len(postScripts) == 0: - return - - script_log.info("Running kickstart %%post script(s)") - for script in postScripts: - script.run(conf.target.system_root) - script_log.info("All kickstart %%post script(s) have been run") - - def runPreScripts(scripts): preScripts = [s for s in scripts if s.type == KS_SCRIPT_PRE] @@ -412,24 +354,3 @@ def runPreScripts(scripts): script.run("/") script_log.info("All kickstart %%pre script(s) have been run") - - -def runPreInstallScripts(scripts): - preInstallScripts = [s for s in scripts if s.type == KS_SCRIPT_PREINSTALL] - - if len(preInstallScripts) == 0: - return - - script_log.info("Running kickstart %%pre-install script(s)") - - for script in preInstallScripts: - script.run("/") - - script_log.info("All kickstart %%pre-install script(s) have been run") - - -def runTracebackScripts(scripts): - script_log.info("Running kickstart %%traceback script(s)") - for script in filter(lambda s: s.type == KS_SCRIPT_TRACEBACK, scripts): - script.run("/") - script_log.info("All kickstart %%traceback script(s) have been run") diff --git a/pyanaconda/modules/common/constants/objects.py b/pyanaconda/modules/common/constants/objects.py index 2deb5890dcb..6eb81effe9a 100644 --- a/pyanaconda/modules/common/constants/objects.py +++ b/pyanaconda/modules/common/constants/objects.py @@ -21,7 +21,12 @@ PARTITIONING_NAMESPACE, DEVICE_TREE_NAMESPACE, \ RHSM_NAMESPACE, RUNTIME_NAMESPACE -# Boss objects. +# Runtime objects + +SCRIPTS = DBusObjectIdentifier( + namespace=RUNTIME_NAMESPACE, + basename="Scripts" +) USER_INTERFACE = DBusObjectIdentifier( namespace=RUNTIME_NAMESPACE, diff --git a/pyanaconda/modules/common/errors/__init__.py b/pyanaconda/modules/common/errors/__init__.py index 190371470aa..ab19025a8e2 100644 --- a/pyanaconda/modules/common/errors/__init__.py +++ b/pyanaconda/modules/common/errors/__init__.py @@ -27,6 +27,7 @@ def register_errors(): installation, module, payload, + runtime, storage, task ) diff --git a/pyanaconda/modules/common/errors/runtime.py b/pyanaconda/modules/common/errors/runtime.py new file mode 100644 index 00000000000..3c64c758aca --- /dev/null +++ b/pyanaconda/modules/common/errors/runtime.py @@ -0,0 +1,31 @@ +# +# DBus errors related to the runtime modules. +# +# Copyright (C) 2024 Red Hat, Inc. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see . +# +from pyanaconda.core.dbus import dbus_error +from pyanaconda.modules.common.constants.namespaces import RUNTIME_NAMESPACE +from pyanaconda.modules.common.errors.general import AnacondaError + + +@dbus_error("ScriptError", namespace=RUNTIME_NAMESPACE) +class ScriptError(AnacondaError): + """error raised during kickstart script processing.""" + def __init__(self, message): + super().__init__(message) + lines = message.split("\n", 1) + self.lineno = lines[0] + self.details = lines[1].strip() if len(lines) > 1 else "" diff --git a/pyanaconda/modules/runtime/Makefile.am b/pyanaconda/modules/runtime/Makefile.am index 5523d644dd3..5bbcfeec7c5 100644 --- a/pyanaconda/modules/runtime/Makefile.am +++ b/pyanaconda/modules/runtime/Makefile.am @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -SUBDIRS = dracut_commands user_interface +SUBDIRS = dracut_commands scripts user_interface pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME) runtimedir = $(pkgpyexecdir)/modules/runtime diff --git a/pyanaconda/modules/runtime/kickstart.py b/pyanaconda/modules/runtime/kickstart.py index 2f7627820dd..e18b0174768 100644 --- a/pyanaconda/modules/runtime/kickstart.py +++ b/pyanaconda/modules/runtime/kickstart.py @@ -17,7 +17,11 @@ # License and may only be used or replicated with the express permission of # Red Hat, Inc. # +from pykickstart.sections import (PreInstallScriptSection, + PostScriptSection, TracebackScriptSection, OnErrorScriptSection) + from pyanaconda.core.kickstart import KickstartSpecification, commands as COMMANDS +from pyanaconda.kickstart import AnacondaKSScript class RuntimeKickstartSpecification(KickstartSpecification): @@ -41,3 +45,10 @@ class RuntimeKickstartSpecification(KickstartSpecification): "DriverDiskData": COMMANDS.DriverDiskData, "SshPwData": COMMANDS.SshPwData, } + + sections = { + "pre-install": lambda handler: PreInstallScriptSection(handler, dataObj=AnacondaKSScript), + "post": lambda handler: PostScriptSection(handler, dataObj=AnacondaKSScript), + "onerror": lambda handler: OnErrorScriptSection(handler, dataObj=AnacondaKSScript), + "traceback": lambda handler: TracebackScriptSection(handler, dataObj=AnacondaKSScript), + } diff --git a/pyanaconda/modules/runtime/runtime.py b/pyanaconda/modules/runtime/runtime.py index ca1d59d1987..bcd9562d6a9 100755 --- a/pyanaconda/modules/runtime/runtime.py +++ b/pyanaconda/modules/runtime/runtime.py @@ -25,6 +25,7 @@ from pyanaconda.modules.runtime.runtime_interface import RuntimeInterface from pyanaconda.modules.runtime.kickstart import RuntimeKickstartSpecification from pyanaconda.modules.runtime.dracut_commands import DracutCommandsModule +from pyanaconda.modules.runtime.scripts import ScriptsModule from pyanaconda.modules.runtime.user_interface import UIModule from pyanaconda.modules.common.base import KickstartService from pyanaconda.modules.common.constants.services import RUNTIME @@ -51,6 +52,9 @@ def __init__(self): self._dracut_module = DracutCommandsModule() self._modules.add_module(self._dracut_module) + self._scripts_module = ScriptsModule() + self._modules.add_module(self._scripts_module) + self._ui_module = UIModule() self._modules.add_module(self._ui_module) diff --git a/pyanaconda/modules/runtime/scripts/Makefile.am b/pyanaconda/modules/runtime/scripts/Makefile.am new file mode 100644 index 00000000000..e4ef93d357c --- /dev/null +++ b/pyanaconda/modules/runtime/scripts/Makefile.am @@ -0,0 +1,21 @@ +# +# Copyright (C) 2024 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +pkgpyexecdir = $(pyexecdir)/py$(PACKAGE_NAME) +scriptsdir = $(pkgpyexecdir)/modules/runtime/scripts +dist_scripts_DATA = $(wildcard $(srcdir)/*.py) + +MAINTAINERCLEANFILES = Makefile.in diff --git a/pyanaconda/modules/runtime/scripts/__init__.py b/pyanaconda/modules/runtime/scripts/__init__.py new file mode 100644 index 00000000000..4f3b772bba6 --- /dev/null +++ b/pyanaconda/modules/runtime/scripts/__init__.py @@ -0,0 +1,20 @@ +# +# Copyright (C) 2024 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.modules.runtime.scripts.scripts import ScriptsModule + +__all__ = ["ScriptsModule"] diff --git a/pyanaconda/modules/runtime/scripts/runtime.py b/pyanaconda/modules/runtime/scripts/runtime.py new file mode 100644 index 00000000000..c0df36a5f47 --- /dev/null +++ b/pyanaconda/modules/runtime/scripts/runtime.py @@ -0,0 +1,58 @@ +# +# Copyright (C) 2024 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.core.configuration.anaconda import conf +from pyanaconda.modules.common.errors.runtime import ScriptError +from pyanaconda.modules.common.task import Task +from pyanaconda.anaconda_loggers import get_module_logger + +from pykickstart.constants import KS_SCRIPT_POST + +log = get_module_logger(__name__) + + +class RunScriptsTask(Task): + """Task for running scripts.""" + + def __init__(self, script_type, scripts): + """Create a new task. + + :param script_type: type of scripts to be run + :type script_type: int + :param scripts: list of scripts + :type scripts: list(Script) + """ + super().__init__() + self._script_type = script_type + self._scripts = scripts + + @property + def name(self): + return "Run scripts" + + def run(self): + """Execute the task.""" + for script in self._scripts: + if script.type == self._script_type: + if script.type == KS_SCRIPT_POST: + result = script.run(conf.target.system_root) + else: + result = script.run("/") + if result: + lineno, err = result + error_message = f"{lineno}\n\n{err.strip()}" + raise ScriptError(error_message) diff --git a/pyanaconda/modules/runtime/scripts/scripts.py b/pyanaconda/modules/runtime/scripts/scripts.py new file mode 100644 index 00000000000..03e13678493 --- /dev/null +++ b/pyanaconda/modules/runtime/scripts/scripts.py @@ -0,0 +1,56 @@ +# +# The user interface module +# +# Copyright (C) 2024 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. +# +# Set up the modules logger. +from pyanaconda.anaconda_loggers import get_module_logger +from pyanaconda.core.dbus import DBus +from pyanaconda.modules.common.base import KickstartBaseModule +from pyanaconda.modules.common.constants.objects import SCRIPTS +from pyanaconda.modules.runtime.scripts.runtime import RunScriptsTask +from pyanaconda.modules.runtime.scripts.scripts_interface import ScriptsInterface + +log = get_module_logger(__name__) + +__all__ = ["ScriptsModule"] + + +class ScriptsModule(KickstartBaseModule): + def __init__(self): + super().__init__() + self._scripts = [] + + def publish(self): + """Publish the module.""" + DBus.publish_object(SCRIPTS.object_path, ScriptsInterface(self)) + + def process_kickstart(self, data): + log.debug("Process_kickstart %s", data.scripts) + self._scripts = data.scripts + + def setup_kickstart(self, data): + log.debug("Setup_kickstart %s", data) + data.scripts = self._scripts + + def run_scripts_with_task(self, script_type): + """Run all scripts of given type sequentially.""" + log.debug("Running %s scripts with task", script_type) + return RunScriptsTask( + script_type=script_type, + scripts=self._scripts + ) diff --git a/pyanaconda/modules/runtime/scripts/scripts_interface.py b/pyanaconda/modules/runtime/scripts/scripts_interface.py new file mode 100644 index 00000000000..87cc013eb26 --- /dev/null +++ b/pyanaconda/modules/runtime/scripts/scripts_interface.py @@ -0,0 +1,49 @@ +# +# DBus interface for the scripts module +# +# Copyright (C) 2021 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 dasbus.server.interface import dbus_interface +from dasbus.typing import * # pylint: disable=wildcard-import + +from pyanaconda.modules.common.base import KickstartModuleInterfaceTemplate +from pyanaconda.modules.common.constants.objects import SCRIPTS +from pyanaconda.modules.common.containers import TaskContainer + +__all__ = ["ScriptsInterface"] + + +@dbus_interface(SCRIPTS.interface_name) +class ScriptsInterface(KickstartModuleInterfaceTemplate): + """DBus interface for the scripts module.""" + + def RunScriptsWithTask(self, script_type: Int) -> ObjPath: + """Run all scripts of given type sequentially with task. + + The types of scripts: + kickstart scripts: + KS_SCRIPT_PRE = 0 + KS_SCRIPT_POST = 1 + KS_SCRIPT_TRACEBACK = 2 + KS_SCRIPT_PREINSTALL = 3 + KS_SCRIPT_ONERROR = 4 + :param script_type: Type of scripts to be run. + :return: a DBus path of the task + """ + return TaskContainer.to_object_path( + self.implementation.run_scripts_with_task(script_type) + ) diff --git a/pyanaconda/rescue.py b/pyanaconda/rescue.py index a3db405d504..8beac72188b 100644 --- a/pyanaconda/rescue.py +++ b/pyanaconda/rescue.py @@ -16,10 +16,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +import sys + from pyanaconda.core import util from pyanaconda.core.configuration.anaconda import conf -from pyanaconda.core.constants import ANACONDA_CLEANUP, THREAD_STORAGE, QUIT_MESSAGE -from pyanaconda.modules.common.constants.objects import DEVICE_TREE +from pyanaconda.core.constants import ANACONDA_CLEANUP, THREAD_STORAGE, QUIT_MESSAGE, IPMI_ABORTED +from pyanaconda.errors import errorHandler +from pyanaconda.modules.common.errors.runtime import ScriptError +from pyanaconda.modules.common.constants.objects import DEVICE_TREE, SCRIPTS from pyanaconda.modules.common.constants.services import STORAGE, RUNTIME from pyanaconda.modules.common.errors.storage import MountFilesystemError from pyanaconda.modules.common.structures.rescue import RescueData @@ -28,11 +32,11 @@ from pyanaconda.core.threads import thread_manager from pyanaconda.flags import flags from pyanaconda.core.i18n import _, N_ -from pyanaconda.kickstart import runPostScripts from pyanaconda.ui.tui import tui_quit_callback from pyanaconda.ui.tui.spokes import NormalTUISpoke from pykickstart.constants import KS_REBOOT, KS_SHUTDOWN +from pykickstart.constants import KS_SCRIPT_POST from simpleline import App from simpleline.render.adv_widgets import YesNoDialog, PasswordDialog @@ -244,7 +248,17 @@ def mount_root(self, root): # run %post if we've mounted everything if not self.ro and self._scripts: - runPostScripts(self._scripts) + scripts_proxy = RUNTIME.get_proxy(SCRIPTS) + post_task_path = scripts_proxy.RunScriptsWithTask(KS_SCRIPT_POST) + post_task_proxy = RUNTIME.get_proxy(post_task_path) + try: + sync_run_task(post_task_proxy) + except ScriptError as e: + flags.ksprompt = True + errorHandler.cb(e) + util.ipmi_report(IPMI_ABORTED) + sys.exit(0) + pass self.status = RescueModeStatus.MOUNTED return True