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.
366 class DeprecatedOption:
368 Creates a new Option type that supports a "deprecated" option
369 attribute. Command options with deprecated=1 will still be
370 processed, but will raise a DeprecationWarning that may be logged.
374 Creates a new KickstartData object and sets default values on
377 class KickstartError:
379 Creates a new KickstartError exception object with the given val
380 as the message. This is a generic exception.
382 class KickstartHandlers:
383 def __init__ (ksdata):
384 Creates a new KickstartHandlers object and initializes the handler
385 dictionary to point at the default handlers.
387 def resetHandlers ():
388 Clears each handler to None. This can be used to quickly clear
389 out the handlers before setting only the handlers a subclass is
392 def deprecatedCommand (cmd):
393 Uses the Python warnings framework to issue a DeprecationWarning
394 for the given command keyword.
397 A dictionary mapping commands to handling methods. Individual
398 handlers may be set to None, in which case the command will be
399 ignored. A subclass may override any or all of the handlers,
400 though each one should call the superclass's method to ensure the
401 ksdata is correct. The default handlers only set ksdata.
403 class KickstartLogVolData:
405 Creates a new KickstartLogVolData object and sets default values
406 on attributes. Instances of this class should be appended to
407 KickstartData.logVolList.
409 class KickstartNetworkData:
411 Creates a new KickstartNetworkData object and sets default values
412 on attributes. Instances of this class should be appended to
413 KickstartData.networkList.
415 class KickstartParser:
416 def __init__ (ksdata, kshandlers):
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.
422 Called when the parser determines that it has found the end of
423 a script section and should add the script to the ksdata's list.
424 This method may be overriden if specialized behavior is required,
425 though the superclass's method should still be called to make sure
426 the script ends up in the ksdata.
428 def addPackages (line):
429 Called while in the %packages section. Takes a line from the
430 file, determines whether it is a group, package, or excluded
431 package, and adds it to the correct list in the ksdata. This
432 method may be overriden if specialized behavior is required,
433 though the superclass's method should still be called to make sure
434 the package lists in the ksdata are correct.
436 def handleCommand (cmd, lineno, args):
437 Called while in the commands section. Takes a line from the
438 file, gets the commands from the head of the line, and dispatches
439 the correct method from the KickstartHandlers. If the
440 KickstartParser object is created with kshandlers=None, no method
441 will be called for the command but no error will be raised. This
442 method may be overridden if specialized behavior is needed.
444 def handlePackageHdr (lineno, args):
445 Called when the %packages section header is first seen. Handles
446 the options that may be given to %packages and sets these values
447 in the ksdata. This method may be overridden if specialized
448 behavior is needed, though the superclass's method should still be
451 def handleScriptHdr (lineno, args):
452 Called when one of the script headers - %pre, %post, or %traceback -
453 is first seen. Handles the optiosn that may be given to those
454 sections headers and sets those falues in the internal script
455 representation. This method may be overridden if specialized
456 behavior is needed, though the superclass's method should still be
459 def readKickstart (file):
460 Parse the input file. This method reads the input file, switches
461 between states, and calls the handlers listed above. This
462 method should not need to be overridden by any subclass as it
463 defines the kickstart file format.
466 Defaults to True. Set to False if you don't want %include
467 files to be parsed as well.
469 class KickstartParseError:
471 Creates a new KickstartParseError exception object. The message
472 will include msg, which should be a properly formatted error
473 message produced by formatErrorMsg.
475 class KickstartPartData:
477 Creates a new KickstartPartData object and sets default values
478 on attributes. Instances of this class should be appended to
479 KickstartData.partList.
481 class KickstartRaidData:
483 Creates a new KickstartRaidData object and sets default values
484 on attributes. Instances of this class should be appended to
485 KickstartData.raidList.
487 class KickstartValueError:
489 Creates a new KickstartValueError exception object corresponding
490 to a problem with the provided val, which should be a properly
491 formatted error message produced by formatErrorMsg.
493 class KickstartVolGroupData:
495 Creates a new KickstartVolGroupData object and sets default values
496 on attributes. Instances of this class should be appended to
497 KickstartData.volGroupList.
499 class KickstartWriter:
500 def __init__ (ksdata):
501 Creates a new KickstartWriter object and initializes the order
502 KickstartData options should be printed out in.
505 Returns a valid kickstart file generated from ksdata as a string
506 that is suitable for writing out to a file.
508 class KSOptionParser:
509 def __init__ (map=[], lineno=None):
510 Creates a specialized subclass of python's OptionParser making use
511 of the MappableOption, RequiredOption, and DeprecatedOption
512 classes. This class is used within command handlers and should
513 not need to be subclassed unless extremely complex behavior is
514 required. The map option is needed if this object will have any
515 MappableOption instances. lineno is used in error reporting.
517 class MappableOption:
519 Creates a new Option type that supports a "map" action. A map
520 action on a given option stores into the destination the value
523 KSOptionParser.map[key]
525 where key is the option with leading dashes stripped off.
527 This also adds a "map_extend" action. A map_extend action on
528 a given set of options appends to the destination the value
531 KSOptionParser.map[key]
533 where key is the option with leading dashes stripped off.
535 class RequiredOption:
537 Creates a new Option type that supports a "required" option
538 attribute. Command options with required=1 must be provided or
539 an OptionError exception is raised.
542 def __init__ (script, interp="/bin/sh", inChroot=False, logfile=None,
543 errorOnFail=False, type=KS_SCRIPT_PRE):
544 Creates a new Script object and sets the defaults for a %pre
545 script. inChroot only makes sense for %post scripts.
548 Returns a string representation of the script useful for debugging
552 Returns a valid kickstart file script generated from ksdata as a
553 string that is suitable for writing out to a file. This string
554 includes the script header.