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 extendable 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 This document will cover how to use pykickstart in your programs and how to
37 extend the basic parser to get customized behavior. It includes a
38 description of the important classes and several working examples.
43 Before diving into the full documentation, it is useful to see an example
44 of how simple it is to use the default pykickstart in your programs. Here
45 is a code snippet that imports the required classes, parses a kickstart
46 file, and leaves the results in the common data format.
49 from pykickstart.data import *
50 from pykickstart.parser import *
52 ksdata = KickstartData()
53 kshandlers = KickstartHandlers(ksdata)
54 ksparser = KickstartParser(ksdata, kshandlers)
55 ksparser.readKickstart("ks.cfg")
57 The call to readKickstart() reads in the kickstart file and sets values in
58 ksdata. After this call, you don't need kshandlers or ksparser anymore
59 but you will want to hold on to ksdata if you are interested in the
60 contents of the file. If you want to do some manipulations and then write
61 out the results to a new file, that's as simple as:
63 from pykickstart.writer import KickstartWriter
64 kswriter = KickstartWriter(ksdata)
65 outfile = open("out.cfg", 'w")
66 outfile.write(kswriter.write())
72 The important classes that make up pykickstart are spread across a handful
77 This file includes no classes, though it does include several important
78 constants representing various things in the KickstartData. You should
79 import its contents like so:
81 from pykickstart.constants import *
85 This file contains the classes that make up the common data
86 representation. The center of the data format is the KickstartData class,
87 which contains many attributes representing the values from the input
88 file. When a new KickstartData is instantiated, these attributes are set
89 to appropriate default values. Some attributes are a simple string or
90 boolean, while some are a list of other classes or dictionaries. For the
91 most part, each lines up with a single kickstart command.
93 The KickstartLogVolData, KickstartNetworkData, KickstartPartData,
94 KickstartRaidData, and KickstartVolGroupData classes are contained as list
95 elements in KickstartData. Each corresponds to a command that may appear
96 multiple times. They exist as separate classes so that attributes are
99 There are three different types of scripts - pre, post, and traceback -
100 but all are represented in the kickstart data by a single list. The
101 Script class is contained in parser.py and contains an attribute that may
102 be used to discriminate between classes.
104 The install package list, excluded package list, and install groups list
105 are kept as separate. Excluded packages have the leading "-" stripped
106 off, and groups have the leading "@" stripped.
108 See the class reference at the end of this documentation for a brief
109 explanation of useful functions in each class.
113 This file represents the bulk of pykickstart code. At its core is the
114 KickstartParser class, which is essentially a state machine. There is one
115 state for each of the sections in a kickstart file, plus some specialized
116 ones to make the parser work. The readKickstart method is the entry point
117 into this class and is designed to be as generic as possible. You should
118 never have to override this method in a subclass. All the other methods
119 handle a change into a specific state and may be overridden in a
122 If the KickstartParser encounters an error while reading your input file,
123 it will raise a KickstartParseError with the line in question. Examples
124 of errors include bad options given to section headers, include files not
125 existing, or headers given for sections that are not recognized (for
128 Error messages should call the formatErrorMsg function to be properly
129 formatted before being sent into an exception. A properly formatted error
130 message includes the line number in the kickstart file where the problem
131 occurred and optionally, a more descriptive message.
133 The other major class within parser.py is KickstartHeaders. This makes up
134 the largest amount of the code and also does the most work as it deals
135 with processing all the options on all the kickstart commands.
136 KickstartHandlers.handlers is a dictionary mapping command names to
137 handling methods. KickstartParser takes the current command and
138 dispatches the correct method based on this mapping. If the command name
139 maps to None, no method is called and no error is issued, a handy feature
140 which will be discussed later in this documentation.
142 Each of the handlers makes use of Python's OptionParser module to parse
143 command arguments and set values in the KickstartData object. For this
144 reason, any subclass of KickstartHandlers must call the superclass's
145 handler to ensure that the KickstartData is correct. Our option parsing
146 code allows commands to be marked as deprecated, which causes a message to
147 be generated and logged by anaconda. It also allows marking options as
148 deprecated or required.
150 There are a few other minor points to note about KickstartParser and
151 KickstartHandlers. When creating a KickstartParser object, you can set
152 the followIncludes attribute to False if you do not wish for include files
153 to be looked up and parsed as well. There are several instances when this
154 is handy. The order of instantiation for these objects is fixed, as each
155 object requires certain ones created before it. KickstartData must be
156 first, as KickstartHandlers and KickstartParser require it.
157 KickstartHandlers must come second, and then finally KickstartParser.
158 Note that you can pass None in for kshandlers in the special case if you
159 do not care about handling any commands at all. As we will see in the
160 next section, this is useful in one special case.
164 This file contains the class that makes up the Kickstart writer. The job
165 of this class is to take a KickstartData object and convert it into a
166 string. This string should be a valid kickstart file that can then be
167 used in any program. Ideally, it should be the same as the input file
168 though the order of options may have been shifted, as well as other
169 cosmetic differences.
171 It is important to note that KickstartWriter.write returns a string, but
172 does no file output. KickstartWriter is laid out similarly to
173 KickstartParser. It consists of one handler per command plus special ones
174 for scripts and packages, plus a list specifying the order these handlers
175 should be called in. It is possible to add your own handlers to
176 KickstartWriter if you are extending kickstart with special commands, as
177 will be discussed in the next section.
179 See the class reference at the end of this documentation for a brief
180 explanation of useful functions in each class.
183 Extending pykickstart
184 =====================
185 By default, pykickstart reads in a kickstart file and sets values in a
186 KickstartData object. This is useful for some applications, but not all.
187 anaconda in particular has some odd requirements for kickstart so it will
188 be our basis for examples on extending pykickstart.
190 Only paying attention to one command
191 ------------------------------------
192 Sometimes, you only want to take action on a single kickstart command and
193 don't care about any of the others. anaconda, for instance, supports a
194 vnc command that needs to be processed well before any of the other
195 commands. Remember from earlier that any command mapping to None in the
196 handlers is skipped. That makes this fairly easy, then. All we need to
197 do is make a specialized KickstartHandlers subclass and hook this into a
200 from pykickstart.data import *
201 from pykickstart.parser import *
203 class VNCHandlers(KickstartHandlers):
204 def __init__ (self, ksdata):
205 KickstartHandlers.__init__(self, ksdata)
207 self.handlers["vnc"] = self.doVnc
209 ksdata = KickstartData()
210 kshandlers = VNCHandlers(ksdata)
211 ksparser = KickstartParser(ksdata, kshandlers)
212 ksparser.readKickstart("ks.cfg")
214 Here, we make use of the KickstartHandlers.resetHandlers method. This
215 method blanks out every entry in the handlers dictionary. We then set the
216 vnc command handler to the default value, which you can easily get from
217 checking out the pykickstart source. Then, make an instance of our new
218 class and pass this to KickstartParser(). When readKickstart is called,
219 the file will be parsed as usual but only the vnc command will ever be
222 You can then check the results by examining ksdata.vnc.
226 In other cases, you may want to include some customized behavior in your
227 kickstart handlers. In this case, you'll still want to create a subclass
228 of KickstartHandlers, though you will need to make sure your handler calls
229 the superclass still.
231 from pykickstart.data import *
232 from pykickstart.parser import *
234 class KSHandlers(KickstartHandlers):
235 def doBootloader (self, args):
236 KickstartHandlers.doBootloader(self, args)
237 print "bootloader location = %s" % self.ksdata.bootloader["location"]
239 ksdata = KickstartData()
240 kshandlers = VNCHandlers(ksdata)
241 ksparser = KickstartParser(ksdata, kshandlers)
242 ksparser.readKickstart("ks.cfg")
244 This example is very simple, but you can still see what would be required
245 for complex cases. Your handler needs to accept an args argument, which
246 is a list of arguments provided to the command. Then, make sure to call
247 the superclass's method of the same name to set the KickstartData.
248 Finally, do your specialized behavior.
250 It is even possible to force your handlers to accept more arguments,
251 though it is slightly more complicated. This requires making a subclass
252 of KickstartParser in addition to KickstartHandlers.
254 from pykickstart.data import *
255 from pykickstart.parser import *
257 class KSHandlers(KickstartHandlers):
258 def doBootloader (self, userarg, args):
259 KickstartHandlers.doBootloader(self, args)
260 print "%s bootloader location = %s" % (userarg, self.ksdata.bootloader["location"])
264 class KSParser(KickstartParser):
265 def __init__ (self, ksdata, kshandlers, userarg):
266 self.userarg = userarg
267 KickstartParser.__init__(self, ksdata, kshandlers)
269 def handleCommand (self, cmd, args):
273 if not self.handler.handlers.has_key(cmd):
274 raise KickstartParseError, (cmd + " " + string.join(args))
276 if self.handler.handlers[cmd] != None:
277 self.handler.currentCmd = cmd
278 self.handler.handlers[cmd](self.userarg,
281 ksdata = KickstartData()
282 kshandlers = VNCHandlers(ksdata)
283 ksparser = KSParser(ksdata, kshandlers, "note: ")
284 ksparser.readKickstart("ks.cfg")
286 Let's examine this example a little more closely. First, you need to
287 create a subclass of KickstartParser whose __init__ method stores your
288 argument. Then you also need to override handleCommand. In the
289 overridden method, the only difference is that on the last line you'll
290 need to pass your argument.
292 In your subclassed KickstartHandlers, you will need to make sure every
293 handler accepts your new argument. You could possibly get around this
294 requirement by further modifying handleCommand to strip off your argument
295 for commands that do not accept it. However, I'm not demonstrating that
296 here. Then, make sure to call the superclass's handler method without any
297 additional arguments.
301 Adding support for a new command is fairly straightforward. You'll need
302 to create a KickstartHandlers and KickstartWriter subclass and add your
303 methods to the handler lists.
305 from pykickstart.data import *
306 from pykickstart.parser import *
307 from pykickstart.writer import *
309 class SpecialHandlers(KickstartHandlers):
310 def __init__ (self, ksdata):
311 KickstartHandlers.__init__(self, ksdata)
312 self.handlers["log"] = self.doLog
314 def doLog (self, args):
315 op = KSOptionParser()
316 op.add_option("--level", dest="level")
317 op.add_option("--host", dest="host")
319 (opts, extra) = op.parse_args(args=args)
321 self.ksdata.log["level"] = getattr(opts, "level")
322 self.ksdata.log["host"] = getattr(opts, "host")
324 class SpecialWriter(KickstartWriter):
325 def __init__ (self, ksdata):
326 KickstartWriter.__init__(self, ksdata)
327 self.handlers.insert(1, self.doLog)
332 if self.ksdata.log["level"]:
333 argstr = "--level=%s" % self.ksdata.log["level"]
334 if self.ksdata.log["host"]:
335 argstr = argstr + "--host=%s" % self.ksdata.log["host"]
338 return "log %s" % argstr
342 ksdata = KickstartData()
343 kshandlers = SpecialHandlers(ksdata)
344 ksparser = KickstartParser(ksdata, kshandlers)
345 ksparser.readKickstart("ks.cfg")
347 This is all fairly straightforward, with the possible exception of the
348 OptionParser stuff. Without getting into it too much, you'll need to
349 create a new instance of KSOptionParser, and then use the Python
350 documentation on how to use it. It's a very complex, powerful class.
351 Make sure to set the KickstartData afterwards.
353 The KickstartWriter object is also pretty simple. Make sure your method
354 returns an empty string if there's nothing set on this option, and the
355 appropriate string otherwise.
359 Currently, there is no simple way to add a new section. This requires
360 adding more code to readKickstart as well as additional states.
361 readKickstart is not set up to do this sort of thing easily.
368 Creates a new KickstartData object and sets default values on
371 class KickstartDmRaidData:
373 Creates a new KickstartDmRaidData object and sets default values on
376 class KickstartError:
378 Creates a new KickstartError exception object with the given val
379 as the message. This is a generic exception.
381 class KickstartHandlers:
382 def __init__ (ksdata):
383 Creates a new KickstartHandlers object and initializes the handler
384 dictionary to point at the default handlers.
386 def resetHandlers ():
387 Clears each handler to None. This can be used to quickly clear
388 out the handlers before setting only the handlers a subclass is
391 def deprecatedCommand (cmd):
392 Uses the Python warnings framework to issue a DeprecationWarning
393 for the given command keyword.
396 A dictionary mapping commands to handling methods. Individual
397 handlers may be set to None, in which case the command will be
398 ignored. A subclass may override any or all of the handlers,
399 though each one should call the superclass's method to ensure the
400 ksdata is correct. The default handlers only set ksdata.
402 class KickstartLogVolData:
404 Creates a new KickstartLogVolData object and sets default values
405 on attributes. Instances of this class should be appended to
406 KickstartData.logVolList.
408 class KickstartNetworkData:
410 Creates a new KickstartNetworkData object and sets default values
411 on attributes. Instances of this class should be appended to
412 KickstartData.networkList.
414 class KickstartParser:
415 def __init__ (ksdata, kshandlers, followIncludes=True,
416 errorsAreFatal=True, missingIncludeIsFatal=True):
417 Creates a new KickstartParser object and initializes the parser
418 state. kshandlers may be None, in which case no function will be
419 run when a kickstart command is seen in the input file.
421 If followIncludes is not set, the parser will ignore %include
422 lines, proceeding as if they were not there. If errorsAreFatal
423 is set, the parser will halt on the first error with a traceback.
424 Otherwise, it will attempt to continue on issuing a warning for
425 each error. This is used in ksvalidator to print out as many
426 errors as possible in a single pass. If missingIncludeIsFatal is
427 not set, the parser will not error if a specified include file
428 does not exist but will still parse included files that do exist.
431 Called when the parser determines that it has found the end of
432 a script section and should add the script to the ksdata's list.
433 This method may be overriden if specialized behavior is required,
434 though the superclass's method should still be called to make sure
435 the script ends up in the ksdata.
437 def addPackages (line):
438 Called while in the %packages section. Takes a line from the
439 file, determines whether it is a group, package, or excluded
440 package, and adds it to the correct list in the ksdata. This
441 method may be overriden if specialized behavior is required,
442 though the superclass's method should still be called to make sure
443 the package lists in the ksdata are correct.
445 def handleCommand (cmd, lineno, args):
446 Called while in the commands section. Takes a line from the
447 file, gets the commands from the head of the line, and dispatches
448 the correct method from the KickstartHandlers. If the
449 KickstartParser object is created with kshandlers=None, no method
450 will be called for the command but no error will be raised. This
451 method may be overridden if specialized behavior is needed.
453 def handlePackageHdr (lineno, args):
454 Called when the %packages section header is first seen. Handles
455 the options that may be given to %packages and sets these values
456 in the ksdata. This method may be overridden if specialized
457 behavior is needed, though the superclass's method should still be
460 def handleScriptHdr (lineno, args):
461 Called when one of the script headers - %pre, %post, or %traceback -
462 is first seen. Handles the optiosn that may be given to those
463 sections headers and sets those falues in the internal script
464 representation. This method may be overridden if specialized
465 behavior is needed, though the superclass's method should still be
468 def readKickstart (file):
469 Parse the input file. This method reads the input file, switches
470 between states, and calls the handlers listed above. This
471 method should not need to be overridden by any subclass as it
472 defines the kickstart file format.
474 class KickstartParseError:
476 Creates a new KickstartParseError exception object. The message
477 will include msg, which should be a properly formatted error
478 message produced by formatErrorMsg.
480 class KickstartPartData:
482 Creates a new KickstartPartData object and sets default values
483 on attributes. Instances of this class should be appended to
484 KickstartData.partList.
486 class KickstartRaidData:
488 Creates a new KickstartRaidData object and sets default values
489 on attributes. Instances of this class should be appended to
490 KickstartData.raidList.
492 class KickstartValueError:
494 Creates a new KickstartValueError exception object corresponding
495 to a problem with the provided val, which should be a properly
496 formatted error message produced by formatErrorMsg.
498 class KickstartVolGroupData:
500 Creates a new KickstartVolGroupData object and sets default values
501 on attributes. Instances of this class should be appended to
502 KickstartData.volGroupList.
504 class KickstartWriter:
505 def __init__ (ksdata):
506 Creates a new KickstartWriter object and initializes the order
507 KickstartData options should be printed out in.
510 Returns a valid kickstart file generated from ksdata as a string
511 that is suitable for writing out to a file.
514 This class extends python's Option class with a variety of extra
515 attributes, types, and actions.
517 deprecated attribute:
518 Command options with deprecated=1 will still be processed, but will
519 raise a DeprecationWarning that may be logged.
522 Allows options to be specified with various boolean values such as
523 yes, no, on, off, 1, and 0.
526 A map action on a given option stores into the destination the value
529 KSOptionParser.map[key]
531 where key is the option with leading dashes stripped off.
534 A map_extend action on a given set of options appends to the
535 destination the value given by:
537 KSOptionParser.map[key]
539 where key is the option with leading dashes stripped off.
542 Command options with required=1 must be provided or an OptionError
545 class KSOptionParser:
546 def __init__ (map={}, lineno=None):
547 Creates a specialized subclass of python's OptionParser making use
548 of the KSOption class. This class is used within command handlers
549 and should not need to be subclassed unless extremely complex
550 behavior is required. The map option is needed if this object will
551 have any map or map_extend actions. lineno is used in error
555 def __init__ (script, interp="/bin/sh", inChroot=False, logfile=None,
556 errorOnFail=False, type=KS_SCRIPT_PRE):
557 Creates a new Script object and sets the defaults for a %pre
558 script. inChroot only makes sense for %post scripts.
561 Returns a string representation of the script useful for debugging
565 Returns a valid kickstart file script generated from ksdata as a
566 string that is suitable for writing out to a file. This string
567 includes the script header.