Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate the kickstart script commands #5739

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,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
79 changes: 79 additions & 0 deletions pyanaconda/core/kickstart/scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#
# 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 = []

Comment on lines +94 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because sections for scripts don't have a data parser like packages does, and sections themselves don't have an action to create an array for scitps. Therefore it is necessary to create it manually. It looks quite strange, but I spent a lot of time on this parsing and unfortunately I didn't come up with anything better using pykickstart

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