Fix --all option to clearpart.
[pykickstart.git] / docs / programmers-guide
blobb9d06c140b8e21c56e89b754822e86941b9a8a60
3                         pykickstart Programmer's Guide
5                                by Chris Lumens
7                               October  28, 2005
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 DeprecatedOption:
367     def __init__ ():
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.
372 class KickstartData:
373     def __init__ ():
374         Creates a new KickstartData object and sets default values on
375         attributes.
377 class KickstartError:
378     def __init__ (val):
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
390         interested in.
392     def deprecatedCommand (cmd):
393         Uses the Python warnings framework to issue a DeprecationWarning
394         for the given command keyword.
396     handlers:
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:
404     def __init__ ():
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:
410     def __init__ ():
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.
421     def addScript ():
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
449         called.
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
457         called.
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.
465     followIncludes:
466         Defaults to True.  Set to False if you don't want %include
467         files to be parsed as well.
469 class KickstartParseError:
470     def __init__ (msg):
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:
476     def __init__ ():
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:
482     def __init__ ():
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:
488     def __init (val):
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:
494     def __init__ ():
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.
504     def write ():
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:
518     def __init__ ():
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
521         given by:
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
529         given by:
531                 KSOptionParser.map[key]
533         where key is the option with leading dashes stripped off.
535 class RequiredOption:
536     def __init__ ():
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.
541 class Script:
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.
547     def __repr__ ():
548         Returns a string representation of the script useful for debugging
549         output.
551     def write ():
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.