3 pykickstart Programmer's Guide
12 pykickstart is a Python library for manipulating kickstart files. It
13 contains a common data representation, a parser, and a writer. This
14 library aims to be useful for all Python programs that need to work with
15 kickstart files. The two most obvious examples are anaconda and
16 system-config-kickstart. It is recommended that all other tools that need
17 to use kickstart files use this library so that we can maintain equivalent
18 levels of support across all tools.
20 The kickstart file format itself has only been defined in a rather ad-hoc
21 manner. Various documents describe the format, commands, and their
22 effects. However, each kickstart-related program implemented its own
23 parser. As anaconda added support for new commands and options, other
24 programs drifted farther and farther out of sync with the "official"
25 format. This leads to the problem that valid kickstart files are not
26 accepted by all programs, or that programs will strip out options it
27 doesn't understand so that the input and output files do not match.
29 pykickstart is an effort to correct this. It was originally designed to
30 be a common code base for anaconda and system-config-kickstart, so making
31 the code generic and easily extensible were top priorities. Another
32 priority was to formalize the currently recognized grammar in an easily
33 understood parser so that files that used to work would continue to. I
34 believe these goals have been met.
36 pykickstart also understands all the various versions of the kickstart syntax
37 that have been around. Various releases of Red Hat Linux, Red Hat Enterprise
38 Linux, and Fedora Core have had slightly different versions. For the most
39 part, the basic syntax has stayed the same. However, different commands have
40 come and gone and different options have been supported on those commands.
41 pykickstart allows specifying which version of kickstart syntax you want
42 to support for reading and writing, allowing you to use one code base to
43 deal with the full range of kickstart files.
45 This document will cover how to use pykickstart in your programs and how to
46 extend the basic parser to get customized behavior. It includes a
47 description of the important classes and several working examples.
52 Before diving into the full documentation, it is useful to see an example
53 of how simple it is to use the default pykickstart in your programs. Here
54 is a code snippet that imports the required classes, parses a kickstart
55 file, and leaves the results in the common data format:
58 from pykickstart.parser import *
59 from pykickstart.version import makeVersion
61 ksparser = KickstartParser(makeVersion())
62 ksparser.readKickstart("ks.cfg")
64 The call to makeVersion() creates a new kickstart handler object for the
65 specified version. By default, it creates one for the latest supported
66 syntax version. The call to KickstartParser() creates a new parser using
67 the handler object for dealing with individual commands. The call to
68 readKickstart() then reads in the kickstart file and sets values in the
71 After this call, all the data from the input kickstart file has been set
72 on the command objects. You can see which objects are available by
73 running dir(ksparser.handler), and then inspect various data settings by
74 examining the contents of each of those objects.
76 The data can be modified if you want. You can then write out the contents
77 to a new file by simply calling:
79 outfile = open("out.cfg", 'w")
80 outfile.write(kshandlers.__str__())
86 The important classes that make up pykickstart are spread across a handful
87 of files. This section includes a brief outline of the contents of those
88 classes. For more thorough documentation, refer to the python doc strings
89 throughout pykickstart. In python, you can view these docs strings like
92 >>> from pykickstart import parser
94 >>> help(parser.KickstartParser)
98 This file contains several basic classes that are used throughout the rest
99 of the library. For the most part, these are abstract classes that are
100 not important to most users of pykickstart. You will really only need to
101 deal with these classes if you are extending kickstart syntax. Other
102 users will mainly only want to look at these classes to see what methods
103 and attributes are provided. This information is also available from the
106 BaseData, BaseHandler, and KickstartCommand are abstract classes that
107 define common methods and attributes. These classes may not be used
108 directly - they can only be used if subclassed. The BaseData and
109 KickstartCommand classes are subclassed to create data objects and command
110 objects. BaseHandler is subclassed to create version handlers that drive
111 the processing of commands and the setting of data.
113 DeprecatedCommand is a subclass of KickstartCommand that may be further
114 used as a subclass for command objects. When one of these subclasses is
115 used, a warning message is printed. Commands that are deprecated are
116 recognized by the parser, but any options given will not be processed and
117 their use causes a warning message to be printed.
121 This file includes no classes, though it does include several important
122 constants representing various things in a kickstart handler class. You
123 should import its contents like so:
125 from pykickstart.constants import *
129 This file contains several useful exceptions and methods. There are four
130 basic exceptions in pykickstart: KickstartError, KickstartParseError,
131 KickstartValueError, and KickstartVersionError.
133 KickstartError is a generic exception, raised on conditions that are not
134 appropriate for any of the other more specific exceptions.
136 If the parser encounters an error while reading your input file, it will
137 raise a KickstartParseError with the line in question. Examples of errors
138 include bad options given to section headers, include files not existing,
139 or headers given for sections that are not recognized (for instance,
140 typos). If the parser encounters an error while processing the arguments
141 to a command, it will raise a KickstartValueError. Examples of these
142 sorts of errors include too many or too few arguments, or missing required
145 KickstartVersionError is only raised by the methods in pykickstart.version
146 if an invalid version is provided by the user.
148 Error messages should call formatErrorMsg() to be properly formatted
149 before being sent into an exception. A properly formatted error message
150 includes the line number in the kickstart file where the problem occurred
151 and optionally, a more descriptive message.
155 This file contains the KSOptionParser and KSOption classes, which are
156 specialized subclasses of OptionParser and Option from python's optparse
157 module. These classes are used extensively throughout the parser and
158 command objects. Specialized subclasses are needed to support required,
159 deprecated, and versioned options; handle specialized error reporting; and
160 support additional option types.
164 This file represents the bulk of pykickstart code. At its core is the
165 KickstartParser class, which is essentially a state machine. There is one
166 state for each of the sections in a kickstart file, plus some specialized
167 ones to make the parser work. The readKickstart() method is the entry point
168 into this class and is designed to be as generic as possible. It reads
169 from the given file name. It is also possible that you may want to read
170 from an existing string, so readKickstartFromString() is also provided.
172 With the exception of _stateMachine(), all the methods in KickstartParser
173 may be overridden in a subclass. _stateMachine() should never be
174 overridden, however, as it provides the core logic for processing
177 There are a few other minor points to note about KickstartParser. When
178 creating a KickstartParser object, you can set the followIncludes
179 attribute to False if you do not wish for include files to be looked up
180 and parsed as well. There are several instances when this is handy. You
181 can also set the missingIncludesIsFatal attribute to False if you want to
182 ignore missing include files. This is most useful when you only care
183 about the main kickstart file (like in ksvalidator, for instance). Note
184 that you can pass None in for kshandlers in the special case if you do not
185 care about handling any commands at all. As we will see later, this is
186 useful in one special case.
188 The Script class represents a single script found in the kickstart file.
189 Since there can be several scripts in a single file, all the instances of
190 Script are stored in a single list. Somewhat confusingly, this list is
191 stored in the handler object provided to KickstartParser when it is
192 instantiated. The script list is not stored in the parser itself. There
193 are three different types of scripts - pre, post, and traceback. The
194 script class contains an attribute that may be used to discriminate among
197 Finally, the parser.py file contains a Packages class for representing the
198 %packages section of the kickstart file. It includes three separate lists
199 - a list of packages to install, a list of packages to exclude, and a list
200 of groups to install. It does not contain anything to handle the header
201 of the %packages section, as this is done by the parser. The Packages
202 instance is held in the same place as the script list.
206 pykickstart supports processing multiple versions of the kickstart syntax
207 from the same code base. In order to make use of this functionality,
208 users must request objects by version number. This file provides the
209 methods and attributes to make this easy. Versions are specified by
210 symbolic names that match up with the names of Fedora or Red Hat
211 Enterprise Linux releases.
213 There is also a special DEVEL version that maps to the latest supported
214 syntax version. All methods in version.py take DEVEL as the implied
215 version, so most people should never even need to deal with specifying
218 stringToVersion() and versionToString() map between strings and these
219 symbolic names. These are provided to make using pykickstart a little
220 easier. stringToVersion() allows you to take the contents of
221 /etc/redhat-release and get a pykickstart version right from that.
223 returnClassForVersion() returns the class that matches a specific version.
224 Most people will not need this capability, as what they are really after
225 is an instance of that class. makeVersion() returns that instance.
230 Kickstart syntax versions are each represented by a file in the handlers/
231 subdirectory. For the most part, these are extremely simple files that
232 define a subclass of the BaseHandler class mentioned above. The names of
233 the handler files are important, but this only matters when adding support
234 for new syntax version. This will be discussed in a later section.
236 The control.py file is a little more complicated, however.
240 This file contains two dictionaries. The commandMap defines a mapping
241 from a syntax version number (as returned by version.stringToVersion()) to
242 another dictionary. This dictionary defines a mapping from a command
243 string to an object that processes that command. Multiple strings may map
244 to the same object, since some kickstart commands have multiple names
245 ("part" and "partition", for instance).
247 The dataMap is set up similarly. It maps syntax version numbers to
248 further dictionaries. These dictionaries map data object names to the
249 objects themselves. Unlike the commandMap, each name may only map to a
250 single object. However, multiple instances of each object can exist at
251 once as these instances are stored in lists. This entire setup is
252 required to handle the data for commands such as "network" and "logvol",
253 which can be specified several times in one kickstart file.
255 The structures in control.py look to be much more verbose than required.
256 Since much data is duplicated among all the various substructures, it
257 seems like this is a perfect place for better object oriented design.
258 However, the duplication is considered a benefit in this one case. It can
259 be difficult to tell which commands are supported by each syntax version,
260 and what object handles those commands. The verbosity in this file makes
261 it very clear exactly which objects will be used by each version of
267 In the commands/ subdirectory you will see many files. Each file
268 corresponds to a single kickstart command. At a minimum, one file will
269 contain a single class that implements the parser, writer, and data store
270 for that command. This command is then entered into the appropriate place
271 in the commandMap from control.py, and then called in the right places by
274 These files may be slightly more complicated, however. Some files contain
275 several classes that all do the same thing. This is because there have
276 been multiple versions of the syntax for that command, and there is one
277 class per version. They are all grouped in the same file for ease of
278 readability, and later versions are allowed to inherit from earlier
279 versions by means of subclassing.
281 Each file may also contain one or more data objects. These data objects
282 are the same as the contents of the dataMap from control.py. There may
283 also be several versions of each data object.
285 At a minimum, the command classes and data classes must implement the
286 methods from KickstartCommand and BaseData. In particular, __init__,
287 __str__, and parse will be called by the KickstartParser. An exception
288 will be raised if one is not defined and the abstract class's method is
291 There are a couple important things to know about command classes. The
292 more complex commands have a lot of data attributes. In writing code to
293 deal with these commands, it can be very tedious to write things like:
295 ksparser.handler.bootloader.forceLBA = True
296 ksparser.handler.bootloader.linear = False
297 ksparser.handler.bootloader.password = "blah"
299 As a shortcut, command classes provide a __call__ method that allows a
300 much more concise and natural way to set a lot of attributes. Any keyword
301 arguments to a command's __init__ method may be passed like this:
303 ksparser.handler.bootloader(forceLBA=True, linear=False, password="blah")
305 Also, all command classes have a writePriority attribute. This controls
306 the order in which commands will be written out when
307 KickstartParser.__str__ is called. This is needed because the order of
308 certain commands matters to anaconda. Lower numbered commands will be
309 written out before higher numbered ones. If several classes have the same
310 priority, they are written in alphabetical order.
313 Extending pykickstart
314 =====================
315 By default, pykickstart reads in a kickstart file and sets values in the
316 command objects. This is useful for some applications, but not all.
317 anaconda in particular has some odd requirements so it will be our basis
318 for examples on extending pykickstart.
320 Only paying attention to one command
321 ------------------------------------
322 Sometimes, you only want to take action on a single kickstart command and
323 don't care about any of the others. anaconda, for instance, supports a
324 vnc command that needs to be processed well before any of the other
325 commands. pykickstart has some functionality to handle this. Version
326 handlers maintain an internal dictionary mapping commands to objects. By
327 setting objects in this dictionary to None, pykickstart knows to ignore
330 Luckily, you don't have to deal with these internal data structures
331 yourself. All that is required is to create a special BaseHandler
332 subclass and mask out all commands except the ones you are interested in:
334 from pykickstart.parser import KickstartParser
335 from pykickstart.version import *
337 superclass = returnClassForVersion()
339 class VNCHandlers(superclass):
340 def __init__(self, mapping={}):
341 superclass.__init__(self, mapping=mapping)
342 self.maskAllExcept(["vnc"])
344 ksparser = KickstartParser(VNCHandlers())
345 ksparser.readKickstart("ks.cfg")
347 Here, we make use of the BaseHandler.maskAllExcept method. This method
348 blanks out the handler for every command except the ones given in the
349 list. Note that the commands are specified by their string
350 representation, not by object reference. We must also be careful when
351 creating the VNCHandler class to make sure it is a subclass. Here, we use
352 the default DEVEL syntax version handler as the superclass.
354 You can then check the results by examining the attributes of
355 ksparser.handler.vnc.
359 In other cases, you may want to include some customized behavior in your
360 kickstart handlers. Due to the use of the commandMap in version.py, this
361 is not as straightforward as it should be. Including specialized behavior
362 for a single handler involves a fairly large amount of code, but
363 specializing the behavior for all handlers does not require much more
366 import pykickstart.commands as commands
367 from pykickstart.handlers.control import commandMap
368 from pykickstart.version import *
370 class Bootloader(commands.bootloader.FC4_Bootloader):
371 def parse(self, args):
372 commands.bootloader.FC4_Bootloader.parse(self, args)
373 print "bootloader location = %s" % self.location
375 commandMap[DEVEL]["bootloader"] = Bootloader
377 superclass = returnClassForVersion()
379 class KSHandlers(superclass):
380 def __init__(self, mapping={}):
381 superclass.__init__(self, mapping=commandMap[DEVEL])
383 ksparser = KickstartParser(KSHandlers())
384 ksparser.readKickstart("ks.cfg")
386 First, we must create a new class for the specialized command object.
387 This class does not have to be a subclass of an already existing handler,
388 but that would require fully writing the parse, __str__, and __init__
389 methods. Instead of doing that, we just subclass it from the latest
390 version of Bootloader.
392 We then import the existing commandMap, overriding the entry for the
393 "bootloader" command with our own new class. We also have to create a
394 special BaseHandler subclass, though here it doesn't do very much. Its
395 only purpose is to deal with our new commandMap. By default, the
396 pykickstart internals will use the commandMap in
397 pykickstart.handlers.control. Since we've modified the mapping, we need
398 to tell pykickstart to use it.
400 It used to be possible to force the handlers, but now it makes more sense
401 to just create a BaseHandler subclass and stick any attributes you need
402 access to into that object. They can then be accessed via the
403 self.handler attribute in any command object. For instance, a handler
404 could be created like so:
406 class SpecialHandler(superclass):
407 def __init__(self, mapping={}):
408 superclass.__init__(self, mapping=mapping)
410 # special data we want to access in command objects
417 Adding a new command to pykickstart is only slightly more complicated than
418 customizing a handler. Here, we create a new hypothetical "confirm"
419 command. If we were to add this into anaconda as well, it might do
420 something such as tell the installer to stop at the confirmation screen
423 from pykickstart.base import *
424 from pykickstart.handlers.control import commandMap
425 from pykickstart.errors import *
426 from pykickstart.parser import KickstartParser
427 from pykickstart.version import *
429 class F7_Confirm(KickstartCommand):
430 def __init__(self, writePriority=0, confirm=False):
431 KickstartCommand.__init__(self, writePriority)
432 self.confirm = confirm
440 def parse(self, args):
442 raise KickstartValueError, formatErrorMsg(self.lineno, msg=("Kickstart command %s does not take any arguments") % "confirm")
446 commandMap[DEVEL]["confirm"] = F7_Confirm
447 superclass = returnClassForVersion()
449 class KSHandlers(superclass):
450 def __init__(self, mapping={}):
451 superclass.__init__(self, mapping=commandMap[DEVEL])
453 ksparser = KickstartParser(KSHandlers())
454 ksparser.readKickstart("ks.cfg")
455 print ksparser.handler.confirm.confim
457 Notice how the command object is subclassed from the base object,
458 KickstartCommand. Its name also has a version number at the beginning.
459 While all command object names in pykickstart take the form
460 Version_CommandName, this is not strictly necessary. Also note how
461 F7_Confirm.__init__ takes a "confirm" keyword argument. All publicly
462 available attributes should be accepted like this for convenience.
466 While multiple version support is one of the major features of
467 pykickstart, adding a new version is more complicated than can be shown in
468 a simple example. The basic requirements would involve first creating a
469 new handler along the lines of those in handlers/*.py, adding new entries
470 to the commandMap and dataMap structures laying out exactly which commands
471 are supported, and then duplicating most of the code in version.py to add
472 the new version number and proper imports.
476 Currently, there is no simple way to add a new section to the kickstart
477 file. This requires adding more code to _stateMachine as well as
478 additional states. _stateMachine is not set up to do this sort of thing