Skip to content

Commit

Permalink
Migrate the %pre-install, %post, %onerror and %traceback scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkankovsky committed Oct 2, 2024
1 parent 86f5c01 commit 425760e
Show file tree
Hide file tree
Showing 20 changed files with 414 additions and 118 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,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
Expand Down
80 changes: 80 additions & 0 deletions pyanaconda/core/kickstart/scripts.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions pyanaconda/core/kickstart/specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion pyanaconda/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

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, \
Expand All @@ -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):
Expand Down
20 changes: 15 additions & 5 deletions pyanaconda/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,23 @@
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
from pyanaconda.core.constants import THREAD_EXCEPTION_HANDLING_TEST, IPMI_FAILED
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__)
Expand Down Expand Up @@ -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:
Expand Down
25 changes: 12 additions & 13 deletions pyanaconda/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
16 changes: 14 additions & 2 deletions pyanaconda/installation_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
Loading

0 comments on commit 425760e

Please sign in to comment.