Skip to content

Commit

Permalink
Support the %pre and %pre-install sections in the kickstart specifica…
Browse files Browse the repository at this point in the history
…tion

Allow to parse and generate %pre and %pre-install sections using a kickstart
specification. This solution will eventually replace the current
implementation.
  • Loading branch information
KKoukiou committed Mar 5, 2024
1 parent 3502968 commit 910c96a
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 9 deletions.
174 changes: 174 additions & 0 deletions pyanaconda/core/kickstart/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#
# Support for %pre, %pre-install sections.
#
# 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 <http://www.gnu.org/licenses/>.
#
from pykickstart.errors import KickstartParseError
from pykickstart.sections import Section

from pyanaconda.core.i18n import _

__all__ = ["PreScriptSection", "PreInstallScriptSection"]


class PreScriptSection(Section):
"""Parser of the %pre sections.
Parses the name of the current %pre section and propagates
all arguments and lines of this section to the pre with the
specified name.
"""
sectionOpen = "%pre"

def __init__(self, handler, **kwargs):
super().__init__(handler, **kwargs)
self.data = None
self.interp = None
self.error_on_fail = None
self.log = None

def handleHeader(self, lineno, args):
"""Handle a header of the current %pre section.
This method is called when the opening tag for a section is
seen. Not all sections will need this method, though all
provided with kickstart include one.
:param lineno: a number of the current line
:param args: a list of strings passed as arguments
"""
super().handleHeader(lineno, args)

if not args:
raise KickstartParseError(
_("Missing name of the %pre section."),
lineno=lineno
)

name = args[1]
arguments = args[2:]
data = getattr(self.handler.pre, name, None)

if not data:
raise KickstartParseError(
_("Unknown name of the %pre section."),
lineno=lineno
)

self.data = data
self.line_number = lineno
self.data.handle_header(arguments, self.line_number)

def handleLine(self, line):
"""Handle one line of the current %pre section.
This method is called for every line of a section. Take
whatever action is appropriate. While this method is not
required to be provided, not providing it does not make
a whole lot of sense.
:param line: a complete line, with any trailing newline
"""
if not self.handler:
return

self.line_number += 1
self.data.handle_line(line, self.line_number)

def finalize(self):
"""Handle the end of the current %pre section.
This method is called when the %end tag for a section is
seen.
"""
super().finalize()
self.data.handle_end()
self.data = None


class PreInstallScriptSection(Section):
"""Parser of the %pre-install sections.
Parses the name of the current %pre-install section and propagates
all arguments and lines of this section to the pre-install with the
specified name.
"""
sectionOpen = "%pre-install"

def __init__(self, handler, **kwargs):
super().__init__(handler, **kwargs)
self.data = None
self.interp = None
self.error_on_fail = None
self.log = None

def handleHeader(self, lineno, args):
"""Handle a header of the current %pre-install section.
This method is called when the opening tag for a section is
seen. Not all sections will need this method, though all
provided with kickstart include one.
:param lineno: a number of the current line
:param args: a list of strings passed as arguments
"""
super().handleHeader(lineno, args)

if not args:
raise KickstartParseError(
_("Missing name of the %pre-install section."),
lineno=lineno
)

name = args[1]
arguments = args[2:]
data = getattr(self.handler.pre_install, name, None)

if not data:
raise KickstartParseError(
_("Unknown name of the %pre-install section."),
lineno=lineno
)

self.data = data
self.line_number = lineno
self.data.handle_header(arguments, self.line_number)

def handleLine(self, line):
"""Handle one line of the current %pre-install section.
This method is called for every line of a section. Take
whatever action is appropriate. While this method is not
required to be provided, not providing it does not make
a whole lot of sense.
:param line: a complete line, with any trailing newline
"""
if not self.handler:
return

self.line_number += 1
self.data.handle_line(line, self.line_number)

def finalize(self):
"""Handle the end of the current %pre-install section.
This method is called when the %end tag for a section is
seen.
"""
super().finalize()
self.data.handle_end()
self.data = None
28 changes: 23 additions & 5 deletions pyanaconda/core/kickstart/specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from pyanaconda.core.kickstart.version import VERSION
from pyanaconda.core.kickstart.addon import AddonSection, AddonRegistry
from pyanaconda.core.kickstart.script import PreScriptSection, PreInstallScriptSection

__all__ = ["KickstartSpecification", "NoKickstartSpecification",
"KickstartSpecificationHandler", "KickstartSpecificationParser"]
Expand Down Expand Up @@ -59,8 +60,8 @@ class KickstartSpecification(object):
version = VERSION
commands = {}
commands_data = {}
sections = {}
sections_data = {}
sections = { "scripts": [] }
sections_data = { "scripts": [] }
addons = {}


Expand All @@ -83,6 +84,9 @@ def __init__(self, specification):
self.registerData(name, data)

for name, data in specification.sections_data.items():
if name is "scripts":
name, data = specification.sections_data.scripts.values()

self.registerSectionData(name, data)

if specification.addons:
Expand All @@ -93,7 +97,12 @@ def __init__(self, specification):

def registerSectionData(self, name, data):
"""Register data used by a section."""
obj = data()
if type(data) is tuple:
class_name, data = data
obj = class_name(data)
else:
obj = data()

setattr(self, name, obj)
self._registerWriteOrder(obj)

Expand All @@ -119,8 +128,17 @@ class KickstartSpecificationParser(KickstartParser):
def __init__(self, handler, specification):
super().__init__(handler)

for section in specification.sections.values():
self.registerSection(section(handler))
for sectionObj in specification.sections.values():
if "scripts" in specification.sections.keys():
sectionObj = specification.sections.scripts.values()

if type(sectionObj) is tuple:
section, dataObj = sectionObj
else:
section = sectionObj
dataObj = None

self.registerSection(section(handler, dataObj=dataObj))

if specification.addons:
self.registerSection(AddonSection(handler))
Expand Down
2 changes: 1 addition & 1 deletion pyanaconda/modules/boss/kickstart_manager/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
TrackedKickstartElements

VALID_SECTIONS_ANACONDA = [
"%pre", "%pre-install", "%post", "%onerror", "%traceback", "%packages", "%addon"
"%pre", "%pre-install", "%post", "%onerror", "%traceback", "%packages", "%scripts", "%addon"
]


Expand Down
67 changes: 64 additions & 3 deletions tests/unit_tests/pyanaconda_tests/test_kickstart_specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
from textwrap import dedent

from pykickstart.base import RemovedCommand
from pykickstart.errors import KickstartParseError
from pykickstart.commands.skipx import FC3_SkipX
from pykickstart.commands.user import F24_User, F19_UserData
from pykickstart.constants import KS_SCRIPT_PRE, KS_SCRIPT_PREINSTALL
from pykickstart.errors import KickstartParseError
from pykickstart.options import KSOptionParser
from pykickstart.parser import Packages
from pykickstart.sections import PackageSection
from pykickstart.parser import Packages, Script
from pykickstart.sections import PackageSection, PreScriptSection, PreInstallScriptSection
from pykickstart.version import F30, isRHEL as is_rhel

from pyanaconda import kickstart
Expand All @@ -49,6 +50,26 @@
from pyanaconda.modules.users.kickstart import UsersKickstartSpecification
from pyanaconda.modules.runtime.kickstart import RuntimeKickstartSpecification

class TestPreScript(Script):
def __init__(self, *args, **kwargs):
script = """
%pre
echo "Hello, world!"
%end
"""
super(TestPreScript, self).__init__(script, *args, **kwargs)
self.type = KS_SCRIPT_PRE

class TestPreInstallScript(Script):
def __init__(self, *args, **kwargs):
script = """
%pre-install
echo "Hello, world!"
%end
"""
super(TestPreInstallScript, self).__init__(script, *args, **kwargs)
self.type = KS_SCRIPT_PREINSTALL


class TestData1(AddonData):

Expand Down Expand Up @@ -210,6 +231,26 @@ class SpecificationF(KickstartSpecification):
"my_test_2": TestData2
}

class SpecificationG(KickstartSpecification):

sections = {
"scripts": [(PreScriptSection, Script)],
}

sections_data = {
"scripts": [TestPreScript],
}

class SpecificationG(KickstartSpecification):

sections = {
"scripts": [(PreInstallScriptSection, Script)]
}

sections_data = {
"scripts": [TestPreInstallScript]
}

def setUp(self):
self.maxDiff = None

Expand Down Expand Up @@ -388,6 +429,26 @@ def test_addons_specification(self):
%end
""")

def test_pre_script_specification(self):
specification = self.SpecificationG

ks_in = """
%pre
echo "Hello, world!"
%end
"""
self.parse_kickstart(specification, ks_in)

def test_pre_install_script_specification(self):
specification = self.SpecificationG

ks_in = """
%pre-install
echo "Hello, world!"
%end
"""
self.parse_kickstart(specification, ks_in)


class ModuleSpecificationsTestCase(unittest.TestCase):
"""Test the kickstart module specifications."""
Expand Down

0 comments on commit 910c96a

Please sign in to comment.