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