Accept --isUtc for backwards compatibility.
[pykickstart.git] / docs / programmers-guide
blob4d57995f9313bcd8d52cb48cd8372f659ade4d83
3                         pykickstart Programmer's Guide
5                                by Chris Lumens
7                               February 16, 2006
10 Introduction
11 ============
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.
41 Getting Started
42 ===============
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.
48         #!/usr/bin/python
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())
67         outfile.close()
70 Classes
71 =======
72 The important classes that make up pykickstart are spread across a handful
73 of files.
75 constants.py
76 ------------
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 *
83 data.py
84 -------
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
97 guaranteed to exist.
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.
111 parser.py
112 ---------
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
120 superclass.
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
126 instance, typos).
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.
162 writer.py
163 ---------
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
198 regular parser:
200         from pykickstart.data import *
201         from pykickstart.parser import *
203         class VNCHandlers(KickstartHandlers):
204                 def __init__ (self, ksdata):
205                         KickstartHandlers.__init__(self, ksdata)
206                         self.resetHandlers()
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
220 handled.
222 You can then check the results by examining ksdata.vnc.
224 Customized handlers
225 -------------------
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"])
262                 ...
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):
270                         if not self.handler:
271                                 return
273                         if not self.handler.handlers.has_key(cmd):
274                                 raise KickstartParseError, (cmd + " " + string.join(args))
275                         else:
276                                 if self.handler.handlers[cmd] != None:
277                                         self.handler.currentCmd = cmd
278                                         self.handler.handlers[cmd](self.userarg,
279 args)
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.
299 Adding a new command
300 --------------------
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)
329                 def doLog(self):
330                         argstr = ""
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"]
337                         if argstr != "":
338                                 return "log %s" % argstr
339                         else
340                                 return
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.
357 Adding a new section
358 --------------------
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.
364 Class Reference
365 ===============
366 class KickstartData:
367     def __init__ ():
368         Creates a new KickstartData object and sets default values on
369         attributes.
371 class KickstartDmRaidData:
372    def __init__ ():
373         Creates a new KickstartDmRaidData object and sets default values on
374         attributes.
376 class KickstartError:
377     def __init__ (val):
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
389         interested in.
391     def deprecatedCommand (cmd):
392         Uses the Python warnings framework to issue a DeprecationWarning
393         for the given command keyword.
395     handlers:
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:
403     def __init__ ():
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:
409     def __init__ ():
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.
430     def addScript ():
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
458         called.
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
466         called.
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:
475     def __init__ (msg):
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:
481     def __init__ ():
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:
487     def __init__ ():
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:
493     def __init (val):
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:
499     def __init__ ():
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.
509     def write ():
510         Returns a valid kickstart file generated from ksdata as a string
511         that is suitable for writing out to a file.
513 class KSOption:
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.
521     ksboolean type:
522         Allows options to be specified with various boolean values such as
523         yes, no, on, off, 1, and 0.
525     map action:
526         A map action on a given option stores into the destination the value
527         given by:
529                 KSOptionParser.map[key]
531         where key is the option with leading dashes stripped off.
533     map_extend action:
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.
541     required attribute:
542         Command options with required=1 must be provided or an OptionError
543         exception is raised.
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
552         reporting.
554 class Script:
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.
560     def __repr__ ():
561         Returns a string representation of the script useful for debugging
562         output.
564     def write ():
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.