2 # sections.py: Kickstart file sections.
4 # Chris Lumens <clumens@redhat.com>
6 # Copyright 2011-2016 Red Hat, Inc.
8 # This copyrighted material is made available to anyone wishing to use, modify,
9 # copy, or redistribute it subject to the terms and conditions of the GNU
10 # General Public License v.2. This program is distributed in the hope that it
11 # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
12 # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 # See the GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along with
16 # this program; if not, write to the Free Software Foundation, Inc., 51
17 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat
18 # trademarks that are incorporated in the source code or documentation are not
19 # subject to the GNU General Public License and may only be used or replicated
20 # with the express permission of Red Hat, Inc.
23 This module exports the classes that define a section of a kickstart file. A
24 section is a chunk of the file starting with a %tag and ending with a %end.
25 Examples of sections include %packages, %pre, and %post.
27 You may use this module to define your own custom sections which will be
28 treated just the same as a predefined one by the kickstart parser. All that
29 is necessary is to create a new subclass of Section and call
30 parser.registerSection with an instance of your new class.
33 from pykickstart.constants import KS_SCRIPT_PRE, KS_SCRIPT_POST, KS_SCRIPT_TRACEBACK, \
34 KS_SCRIPT_PREINSTALL, KS_SCRIPT_ONERROR, \
35 KS_MISSING_IGNORE, KS_MISSING_PROMPT, \
36 KS_BROKEN_IGNORE, KS_BROKEN_REPORT, KS_SCRIPT_RUN, KS_SCRIPT_UMOUNT
37 from pykickstart.errors import KickstartError, KickstartParseError, KickstartDeprecationWarning
38 from pykickstart.options import KSOptionParser
39 from pykickstart.version import FC4, F7, F9, F18, F21, F22, F24, F32, F34, RHEL6, RHEL7, RHEL9, \
41 from pykickstart.i18n import _
43 class Section(object):
44 """The base class for defining kickstart sections. You are free to
45 subclass this as appropriate.
49 allLines -- Does this section require the parser to call handleLine
50 for every line in the section, even blanks and comments?
51 sectionOpen -- The string that denotes the start of this section. You
52 must start your tag with a percent sign.
53 timesSeen -- This attribute is for informational purposes only. It is
54 incremented every time handleHeader is called to keep
55 track of the number of times a section of this type is
62 def __init__(self, handler, **kwargs):
63 """Create a new Script instance. At the least, you must pass in an
64 instance of a baseHandler subclass.
68 dataObj -- A class that should be populated by this Section. It almost
69 always should be Script, or some subclass of it.
71 self.handler = handler
72 self.version = self.handler.version
74 self.dataObj = kwargs.get("dataObj", None)
77 """This method is called when the %end tag for a section is seen. It
78 is not required to be provided.
81 def handleLine(self, line):
82 """This method is called for every line of a section. Take whatever
83 action is appropriate. While this method is not required to be
84 provided, not providing it does not make a whole lot of sense.
88 line -- The complete line, with any trailing newline.
91 # pylint: disable=unused-argument
92 def handleHeader(self, lineno, args):
93 """This method is called when the opening tag for a section is seen.
94 Not all sections will need this method, though all provided with
95 kickstart include one.
99 args -- A list of all strings passed as arguments to the section
103 # pylint: enable=unused-argument
107 """This property is given for consistency with KickstartCommand objects
108 only. It simply returns whether timesSeen is non-zero.
110 return self.timesSeen > 0
112 class NullSection(Section):
113 """This defines a section that pykickstart will recognize but do nothing
114 with. If the parser runs across a %section that has no object registered,
115 it will raise an error. Sometimes, you may want to simply ignore those
116 sections instead. This class is useful for that purpose.
120 def __init__(self, *args, **kwargs):
121 """Create a new NullSection instance. You must pass a sectionOpen
122 parameter (including a leading '%') for the section you wish to
125 Section.__init__(self, *args, **kwargs)
126 self.sectionOpen = kwargs.get("sectionOpen")
130 def handleHeader(self, lineno, args):
133 def handleLine(self, line):
134 self._body.append(line)
137 body = "\n".join(self._body)
139 s = "%s\n%s\n%%end" % (" ".join(self._args), body)
141 s = "%s\n%%end" % " ".join(self._args)
143 self.handler._null_section_strings.append(s)
148 class ScriptSection(Section):
154 def __init__(self, *args, **kwargs):
155 Section.__init__(self, *args, **kwargs)
159 def _getParser(self):
160 op = KSOptionParser(prog=self.sectionOpen,
161 description=self._description,
165 op.add_argument("--erroronfail", dest="errorOnFail", action="store_true",
166 default=False, help="""
167 If the error script fails, this option will cause an
168 error dialog to be displayed and will halt installation.
169 The error message will direct you to where the cause of
170 the failure is logged.""", version=FC4)
171 op.add_argument("--interpreter", dest="interpreter", default="/bin/sh",
172 version=FC4, metavar="/usr/bin/python", help="""
173 Allows you to specify a different scripting language,
174 such as Python. Replace /usr/bin/python with the
175 scripting language of your choice.
177 op.add_argument("--log", "--logfile", dest="log", version=FC4,
179 Log all messages from the script to the given log file.
183 def _resetScript(self):
184 self._script = {"interp": "/bin/sh", "log": None, "errorOnFail": False,
185 "lineno": None, "chroot": False, "body": []}
187 def handleLine(self, line):
188 self._script["body"].append(line)
191 if " ".join(self._script["body"]).strip() == "":
194 kwargs = {"interp": self._script["interp"],
195 "inChroot": self._script["chroot"],
196 "lineno": self._script["lineno"],
197 "logfile": self._script["log"],
198 "errorOnFail": self._script["errorOnFail"],
199 "type": self._script["type"]}
201 if self.dataObj is not None:
202 s = self.dataObj(self._script["body"], **kwargs)
204 self.handler.scripts.append(s)
206 def handleHeader(self, lineno, args):
207 """Process the arguments to a %pre/%post/%traceback header for later
208 setting on a Script instance once the end of the script is found.
209 This method may be overridden in a subclass if necessary.
211 Section.handleHeader(self, lineno, args)
212 op = self._getParser()
214 ns = op.parse_args(args=args[1:], lineno=lineno)
216 self._script["interp"] = ns.interpreter
217 self._script["lineno"] = lineno
218 self._script["log"] = ns.log
219 self._script["errorOnFail"] = ns.errorOnFail
220 if hasattr(ns, "nochroot"):
221 self._script["chroot"] = not ns.nochroot
223 class PreScriptSection(ScriptSection):
225 _title = "Pre-installation script"
227 You can add commands to run on the system immediately after the ks.cfg
228 has been parsed and the lang, keyboard, and url options have been
229 processed. This section must be at the end of the kickstart file (after
230 the commands) and must start with the %pre command. You can access the
231 network in the %pre section; however, name service has not been
232 configured at this point, so only IP addresses will work.
234 Preinstallation scripts are required to be closed with %end.
236 If your script spawns a daemon process, you must make sure to close
237 ``stdout`` and ``stderr``. Doing so is standard procedure for creating
238 daemons. If you do not close these file descriptors, the installation
239 will appear hung as anaconda waits for an EOF from the script.
243 The pre-installation script is not run in the chroot environment.
249 Here is an example %pre section::
256 for file in /sys/block/sd*; do
257 hds="$hds $(basename $file)"
263 drive1=$(echo $hds | cut -d' ' -f1)
264 drive2=$(echo $hds | cut -d' ' -f2)
267 if [ $numhd == "2" ] ; then
268 echo "#partitioning scheme generated in %pre for 2 drives" > /tmp/part-include
269 echo "clearpart --all" >> /tmp/part-include
270 echo "part /boot --fstype ext4 --size 512 --ondisk sda" >> /tmp/part-include
271 echo "part / --fstype ext4 --size 10000 --grow --ondisk sda" >> /tmp/part-include
272 echo "part swap --recommended --ondisk $drive1" >> /tmp/part-include
273 echo "part /home --fstype ext4 --size 10000 --grow --ondisk sdb" >> /tmp/part-include
275 echo "#partitioning scheme generated in %pre for 1 drive" > /tmp/part-include
276 echo "clearpart --all" >> /tmp/part-include
277 echo "part /boot --fstype ext4 --size 521" >> /tmp/part-include
278 echo "part swap --recommended" >> /tmp/part-include
279 echo "part / --fstype ext4 --size 2048" >> /tmp/part-include
280 echo "part /home --fstype ext4 --size 2048 --grow" >> /tmp/part-include
284 This script determines the number of hard drives in the system and
285 writes a text file with a different partitioning scheme depending on
286 whether it has one or two drives. Instead of having a set of
287 partitioning commands in the kickstart file, include the line:
289 ``%include /tmp/part-include``
291 The partitioning commands selected in the script will be used.
294 def _resetScript(self):
295 ScriptSection._resetScript(self)
296 self._script["type"] = KS_SCRIPT_PRE
298 class PreInstallScriptSection(ScriptSection):
299 sectionOpen = "%pre-install"
300 _title = "Pre-install Script"
302 You can use the %pre-install section to run commands after the system has been
303 partitioned, filesystems created, and everything is mounted under /mnt/sysimage
304 Like %pre these scripts do not run in the chrooted environment.
306 Each %pre-install section is required to be closed with a corresponding %end.
309 def _resetScript(self):
310 ScriptSection._resetScript(self)
311 self._script["type"] = KS_SCRIPT_PREINSTALL
313 class PostScriptSection(ScriptSection):
314 sectionOpen = "%post"
315 _title = "Post-installation Script"
317 You have the option of adding commands to run on the system once the
318 installation is complete. This section must be at the end of the
319 kickstart file and must start with the %post command. This section is
320 useful for functions such as installing additional software and
321 configuring an additional nameserver.
323 You may have more than one %post section, which can be useful for cases
324 where some post-installation scripts need to be run in the chroot and
325 others that need access outside the chroot.
327 Each %post section is required to be closed with a corresponding %end.
329 If you configured the network with static IP information, including a
330 nameserver, you can access the network and resolve IP addresses in the %post
331 section. If you configured the network for DHCP, the /etc/resolv.conf file
332 has not been completed when the installation executes the %post section. You
333 can access the network, but you can not resolve IP addresses. Thus, if you
334 are using DHCP, you must specify IP addresses in the %post section.
336 If your script spawns a daemon process, you must make sure to close stdout
337 and stderr. Doing so is standard procedure for creating daemons. If you do
338 not close these file descriptors, the installation will appear hung as
339 anaconda waits for an EOF from the script.
341 The post-install script is run in a chroot environment; therefore, performing
342 tasks such as copying scripts or RPMs from the installation media will not
350 Run a script named ``runme`` from an NFS share::
354 mount 10.10.0.2:/usr/new-machines /mnt/temp
355 open -s -w -- /mnt/temp/runme
359 Copy the file /etc/resolv.conf to the file system that was just
363 cp /etc/resolv.conf /mnt/sysimage/etc/resolv.conf
366 **If your kickstart is being interpreted by the livecd-creator tool, you should
367 replace /mnt/sysimage above with $INSTALL_ROOT.**
370 def _getParser(self):
371 op = ScriptSection._getParser(self)
372 op.add_argument("--nochroot", dest="nochroot", action="store_true",
373 default=False, version=FC4, help="""
374 Allows you to specify commands that you would like to
375 run outside of the chroot environment.""")
378 def _resetScript(self):
379 ScriptSection._resetScript(self)
380 self._script["chroot"] = True
381 self._script["type"] = KS_SCRIPT_POST
383 class OnErrorScriptSection(ScriptSection):
384 sectionOpen = "%onerror"
385 _title = "Handling Errors"
387 These scripts run when the installer hits a fatal error, but not necessarily
388 a bug in the installer. Some examples of these situations include errors in
389 packages that have been requested to be installed, failures when starting VNC
390 when requested, and error when scanning storage. When these situations happen,
391 installation cannot continue. The installer will run all %onerror scripts in
392 the order they are provided in the kickstart file.
394 In addition, %onerror scripts will be run on a traceback as well. To be exact,
395 all %onerror scripts will be run and then all %traceback scripts will be run
398 Each %onerror script is required to be closed with a corresponding %end.
402 These scripts could potentially run at
403 any stage in installation - early on, between making filesystems and installing
404 packages, before the bootloader is installed, when attempting to reboot, and
405 so on. For this reason, these scripts cannot be run in the chroot environment
406 and you should not trust anything in the installed system. These scripts are
407 primarily for testing and error reporting purposes.
410 def _resetScript(self):
411 ScriptSection._resetScript(self)
412 self._script["type"] = KS_SCRIPT_ONERROR
414 class TracebackScriptSection(OnErrorScriptSection):
415 sectionOpen = "%traceback"
416 _title = "Handling Tracebacks"
418 These scripts run when the installer hits an internal error (a traceback, as
419 they are called in Python) and cannot continue. When this situation happens,
420 the installer will display an error dialog to the screen that prompts the user
421 to file a bug or reboot. At the same time, it will run all %traceback scripts
422 in the order they are provided in the kickstart file.
424 Each %traceback script is required to be closed with a corresponding %end.
428 These scripts could potentially run at
429 any stage in installation - early on, between making filesystems and installing
430 packages, before the bootloader is installed, when attempting to reboot, and
431 so on. For this reason, these scripts cannot be run in the chroot environment
432 and you should not trust anything in the installed system. These scripts are
433 primarily for testing and error reporting purposes.
436 def _resetScript(self):
437 OnErrorScriptSection._resetScript(self)
438 self._script["type"] = KS_SCRIPT_TRACEBACK
440 def handleHeader(self, lineno, args):
441 super().handleHeader(lineno, args)
443 if self.version < F34:
446 warnings.warn("The %traceback section has been deprecated. It may be removed in the "
447 "future, which will result in a fatal error from kickstart. Please modify "
448 "your kickstart file to use the %onerror section instead.",
449 KickstartDeprecationWarning)
451 class RunScriptSection(ScriptSection):
452 sectionOpen = "%runscript"
453 def _resetScript(self):
454 ScriptSection._resetScript(self)
455 self._script["type"] = KS_SCRIPT_RUN
458 ScriptSection.finalize(self)
460 for s in self.handler.scripts:
461 if s.type == KS_SCRIPT_UMOUNT:
462 raise KickstartError("%runscript and %post-umount " \
463 "can not be defined together")
465 class PostUmountScriptSection(ScriptSection):
466 sectionOpen = "%post-umount"
468 def _resetScript(self):
469 ScriptSection._resetScript(self)
470 self._script["type"] = KS_SCRIPT_UMOUNT
473 ScriptSection.finalize(self)
475 for s in self.handler.scripts:
476 if s.type == KS_SCRIPT_RUN:
477 raise KickstartError("%runscript and %post-umount " \
478 "can not be defined together")
480 class PackageSection(Section):
481 sectionOpen = "%packages"
482 _title = "Package Selection"
484 def handleLine(self, line):
485 h = line.partition('#')[0]
487 self.handler.packages.add([line])
489 def _getParser(self):
490 op = KSOptionParser(prog=self.sectionOpen, description="""
491 Use the %packages command to begin a kickstart file
492 section that lists the packages you would like to
495 Packages can be specified by group or by individual
496 package name. The installation program defines
497 several groups that contain related packages. Refer
498 to the repodata/\\*comps\\*.xml file on the
499 installation media or in installation repository
500 for a list of groups. Each group has an id, user
501 visibility value, name, description, and package
502 list. In the package list, the packages marked as
503 mandatory are always installed if the group is
504 selected, the packages marked default are selected
505 by default if the group is selected, and the packages
506 marked optional must be specifically selected even
507 if the group is selected to be installed.
509 In most cases, it is only necessary to list the
510 desired groups and not individual packages. Note
511 that the Core group is always selected by default,
512 so it is not necessary to specify it in the
515 The %packages section is required to be closed with
516 %end. Also, multiple %packages sections may be given.
517 This may be handy if the kickstart file is used as a
518 template and pulls in various other files with the
521 Here is an example %packages selection::
525 @GNOME Desktop Environment
531 As you can see, groups are specified, one to a line,
532 starting with an ``@`` symbol followed by the full
533 group name as given in the comps.xml file. Groups
534 can also be specified using the id for the group,
535 such as gnome-desktop. Specify individual packages
536 with no additional characters (the dhcp line in the
537 example above is an individual package).
539 The ``@`` prefix is also used to request installation
540 of module streams in the following format::
542 @<module name>:<stream name>/<profile name>
544 Profile name is optional and multiple profiles can be
545 installed by using multiple lines, one per profile.
546 Stream name is only optional only if the given module
547 has a default stream.
549 If there are a module and a group named the same,
550 and no stream name and profile are specified,
551 module will be selected instead of a group.
553 Requesting one module more than once with different
554 streams or not specifying a stream name for a module
555 without a default stream will result in an error.
557 Here is an example %packages selection with modules::
560 @^Fedora Server Edition
563 @postgresql:9.6/server
571 You can also specify environments using the ``@^``
572 prefix followed by full environment name as given in
573 the comps.xml file. If multiple environments are
574 specified, only the last one specified will be used.
575 Environments can be mixed with both group
576 specifications (even if the given group is not part
577 of the specified environment) and package
580 Here is an example of requesting the GNOME Desktop
581 environment to be selected for installation::
584 @^gnome-desktop-environment
587 Additionally, individual packages may be specified
588 using globs. For instance::
595 This would install all packages whose names start
596 with "vim" or "kde-i18n-".
598 You can also specify which packages or groups not to
599 install from the default package list::
609 In addition, group lines in the %packages section
610 can take the following options:
614 Only install the group's mandatory packages, not
615 the default selections.
619 In addition to the mandatory and default packages,
620 also install the optional packages. This means all
621 packages in the group will be installed.
624 op.add_argument("--excludedocs", action="store_true", default=False,
626 Do not install any of the documentation from any packages.
627 For the most part, this means files in /usr/share/doc*
628 will not get installed though it could mean other files
629 as well, depending on how the package was built.""",
631 op.add_argument("--ignoremissing", action="store_true", default=False,
633 Ignore any packages, groups or module streams specified in the
634 packages section that are not found in any configured repository.
635 The default behavior is to halt the installation and ask
636 the user if the installation should be aborted or
637 continued. This option allows fully automated
638 installation even in the error case.""",
640 op.add_argument("--ignoredeps", dest="resolveDeps", action="store_false",
641 deprecated=FC4, help="")
642 op.add_argument("--resolvedeps", dest="resolveDeps", action="store_true",
643 deprecated=FC4, help="")
645 if self.version < F7:
648 op.add_argument("--default", dest="defaultPackages", action="store_true",
649 default=False, version=F7, help="""
650 Install the default environment. This corresponds to the
651 package set that would be installed if no other
652 selections were made on the package customization screen
653 during an interactive install.""")
655 if self.version < F9:
658 op.remove_argument("--ignoredeps", version=F9)
659 op.remove_argument("--resolvedeps", version=F9)
660 op.add_argument("--instLangs", default=None, version=F9, help="""
661 Specify the list of languages that should be installed.
662 This is different from the package group level
663 selections, though. This option does not specify what
664 package groups should be installed. Instead, it controls
665 which translation files from individual packages should
666 be installed by setting RPM macros.""")
668 if self.version < RHEL6:
671 op.add_argument("--nobase", action="store_true", default=False,
672 version=RHEL6, help="""
673 Do not install the @base group (installed by default,
676 if self.version < F18:
679 op.add_argument("--nobase", action="store_true", default=False,
681 op.add_argument("--multilib", dest="multiLib", action="store_true",
682 default=False, version=F18, help="""
683 Enable yum's "all" multilib_policy as opposed to the
684 default of "best".""")
686 if self.version < F21:
689 op.add_argument("--nocore", action="store_true", default=False,
690 version=F21, help="""
691 Do not install the @core group (installed by default,
694 **Omitting the core group can produce a system that is
695 not bootable or that cannot finish the install. Use
698 if self.version < RHEL7:
701 op.add_argument("--timeout", dest="timeout", type=int,
702 default=None, version=RHEL7, help="""
703 Set up yum's or dnf's timeout. It is a number of seconds
704 to wait for a connection before timing out.""")
705 op.add_argument("--retries", dest="retries", type=int,
706 default=None, version=RHEL7, help="""
707 Set up yum's or dnf's retries. It is a number of times
708 any attempt to retrieve a file should retry before
709 returning an error.""")
711 if self.version < F22:
714 op.remove_argument("--nobase", version=F22)
716 if self.version < F24:
719 op.add_argument("--excludeWeakdeps", dest="excludeWeakdeps",
720 action="store_true", default=False, version=F24,
722 Do not install packages from weak dependencies. These
723 are packages linked to the selected package set by
724 Recommends and Supplements flags. By default weak
725 dependencies will be installed.""")
727 if self.version < F32:
730 op.add_argument("--instLangs", "--inst-langs", dest="instLangs", default=None,
731 version=F32, help="Added ``--inst-langs`` alias")
733 op.add_argument("--excludeWeakdeps", "--exclude-weakdeps", dest="excludeWeakdeps",
734 action="store_true", default=False, version=F32,
735 help="Added ``--exclude-weakdeps`` alias")
737 op.add_argument("--ignorebroken", action="store_true", default=False, version=F32,
739 Ignore any packages, groups or modules with conflicting files.
740 This issue will disable the DNF `strict` option. The default behavior
741 is to abort the installation with error message describing the
744 **WARNING: Usage of this parameter is DISCOURAGED! The DNF
745 strict option will NOT log any information about what packages
746 were skipped. Using this option may result in an unusable system.**
749 if isRHEL(self.version):
750 # The --ignorebroken feature is not supported on RHEL.
751 op.remove_argument("--ignorebroken", version=RHEL9)
755 def handleHeader(self, lineno, args):
756 """Process the arguments to the %packages header and set attributes
757 on the Version's Packages instance appropriate. This method may be
758 overridden in a subclass if necessary.
760 Section.handleHeader(self, lineno, args)
761 op = self._getParser()
762 ns = op.parse_args(args=args[1:], lineno=lineno)
764 self.handler.packages.seen = True
765 self.handler.packages.excludeDocs = ns.excludedocs
767 self.handler.packages.handleMissing = KS_MISSING_IGNORE
769 self.handler.packages.handleMissing = KS_MISSING_PROMPT
771 if self.version < F7:
774 if ns.defaultPackages:
775 self.handler.packages.default = True
777 if self.version < F9:
780 if ns.instLangs is not None:
781 self.handler.packages.instLangs = ns.instLangs
783 if self.version < RHEL6:
786 if ns.defaultPackages and getattr(ns, "nobase", False):
787 raise KickstartParseError(_("--default and --nobase cannot be used together"), lineno=lineno)
789 self.handler.packages.addBase = not getattr(ns, "nobase", False)
791 if self.version < F18:
794 self.handler.packages.multiLib = ns.multiLib
796 if self.version < F21:
799 if ns.defaultPackages and ns.nocore:
800 raise KickstartParseError(_("--default and --nocore cannot be used together"), lineno=lineno)
802 self.handler.packages.nocore = ns.nocore
804 if self.version < RHEL7:
807 self.handler.packages.timeout = ns.timeout
808 self.handler.packages.retries = ns.retries
810 if self.version < F24:
813 self.handler.packages.excludeWeakdeps = ns.excludeWeakdeps
815 if self.version < F32:
818 if getattr(ns, "ignorebroken", False):
819 self.handler.packages.handleBroken = KS_BROKEN_IGNORE
821 self.handler.packages.handleBroken = KS_BROKEN_REPORT
824 for option, new_option in \
825 {"--instLangs": "--inst-langs", "--excludeWeakdeps": "--exclude-weakdeps"}.items():
827 warnings.warn(_("The %(option)s option on line %(lineno)s will be deprecated in "
828 "future releases. Please modify your kickstart file to replace "
829 "this option with its preferred alias %(new_option)s.")
830 % {"option": option, "lineno": lineno, "new_option": new_option},
831 KickstartDeprecationWarning)
833 class TpkPackageSection(Section):
834 sectionOpen = "%tpk_packages"
836 def handleLine(self, line):
840 (h, s, t) = line.partition('#')
842 self.handler.tpk_packages.add([line])
844 def handleHeader(self, lineno, args):
845 """Process the arguments to the %tpk_packages header and set attributes
846 on the Version's TpkPackages instance appropriate. This method may be
847 overridden in a subclass if necessary.
849 Section.handleHeader(self, lineno, args)