2006-09-20 Jeremy Katz <katzj@redhat.com>
[pykickstart.git] / pykickstart / parser.py
blobe08f9082230371abe9dcaeba9e96baad260f8078
2 # parser.py: Kickstart file parser.
4 # Chris Lumens <clumens@redhat.com>
6 # Copyright 2005, 2006 Red Hat, Inc.
8 # This software may be freely redistributed under the terms of the GNU
9 # general public license.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 import shlex
17 import sys
18 import string
19 import warnings
20 from copy import copy
21 from optparse import *
23 from constants import *
24 from data import *
26 from rhpl.translate import _
27 import rhpl.translate as translate
29 translate.textdomain("pykickstart")
31 STATE_END = 0
32 STATE_COMMANDS = 1
33 STATE_PACKAGES = 2
34 STATE_SCRIPT_HDR = 3
35 STATE_PRE = 4
36 STATE_POST = 5
37 STATE_TRACEBACK = 6
39 ###
40 ### ERROR HANDLING
41 ###
43 def formatErrorMsg(lineno, msg=""):
44 if msg != "":
45 return _("The following problem occurred on line %s of the kickstart file:\n\n%s\n") % (lineno, msg)
46 else:
47 return _("There was a problem reading from line %s of the kickstart file") % lineno
49 class KickstartError(Exception):
50 def __init__(self, val = ""):
51 Exception.__init__(self)
52 self.value = val
54 def __str__ (self):
55 return self.value
57 class KickstartParseError(KickstartError):
58 def __init__(self, msg):
59 KickstartError.__init__(self, msg)
61 def __str__(self):
62 return self.value
64 class KickstartValueError(KickstartError):
65 def __init__(self, msg):
66 KickstartError.__init__(self, msg)
68 def __str__ (self):
69 return self.value
71 ###
72 ### OPTION HANDLING
73 ###
75 # Specialized OptionParser, mainly to handle the MappableOption and to turn
76 # off help.
77 class KSOptionParser(OptionParser):
78 def exit(self, status=0, msg=None):
79 pass
81 def error(self, msg):
82 if self.lineno != None:
83 raise KickstartParseError, formatErrorMsg(self.lineno, msg=msg)
84 else:
85 raise KickstartParseError, msg
87 def keys(self):
88 retval = []
90 for opt in self.option_list:
91 if opt not in retval:
92 retval.append(opt.dest)
94 return retval
96 def _init_parsing_state (self):
97 OptionParser._init_parsing_state(self)
98 self.option_seen = {}
100 def check_values (self, values, args):
101 for option in self.option_list:
102 if (isinstance(option, Option) and option.required and \
103 not self.option_seen.has_key(option)):
104 raise KickstartValueError, formatErrorMsg(self.lineno, _("Option %s is required") % option)
105 elif isinstance(option, Option) and option.deprecated and \
106 self.option_seen.has_key(option):
107 warnings.warn(_("Ignoring deprecated option on line %s: The %s option has been deprecated and no longer has any effect. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to remove this option.") % (self.lineno, option), DeprecationWarning)
109 return (values, args)
111 def __init__(self, map={}, lineno=None):
112 self.map = map
113 self.lineno = lineno
114 OptionParser.__init__(self, option_class=KSOption,
115 add_help_option=False)
117 # Creates a new Option class that supports two new attributes:
118 # - required: any option with this attribute must be supplied or an exception
119 # is thrown
120 # - deprecated: any option with this attribute will cause a DeprecationWarning
121 # to be thrown if the option is used
122 # Also creates a new type:
123 # - ksboolean: support various kinds of boolean values on an option
124 # And two new actions:
125 # - map : allows you to define an opt -> val mapping such that dest gets val
126 # when opt is seen
127 # - map_extend: allows you to define an opt -> [val1, ... valn] mapping such
128 # that dest gets a list of vals built up when opt is seen
129 class KSOption (Option):
130 ATTRS = Option.ATTRS + ['deprecated', 'required']
131 ACTIONS = Option.ACTIONS + ("map", "map_extend",)
132 STORE_ACTIONS = Option.STORE_ACTIONS + ("map", "map_extend",)
134 TYPES = Option.TYPES + ("ksboolean",)
135 TYPE_CHECKER = copy(Option.TYPE_CHECKER)
137 def _check_required(self):
138 if self.required and not self.takes_value():
139 raise OptionError(_("Required flag set for option that doesn't take a value"), self)
141 def _check_ksboolean(option, opt, value):
142 if value.lower() in ("on", "yes", "true", "1"):
143 return True
144 elif value.lower() in ("off", "no", "false", "0"):
145 return False
146 else:
147 raise OptionValueError(_("Option %s: invalid boolean value: %r") % (opt, value))
149 # Make sure _check_required() is called from the constructor!
150 CHECK_METHODS = Option.CHECK_METHODS + [_check_required]
151 TYPE_CHECKER["ksboolean"] = _check_ksboolean
153 def process (self, opt, value, values, parser):
154 Option.process(self, opt, value, values, parser)
155 parser.option_seen[self] = 1
157 # Override default take_action method to handle our custom actions.
158 def take_action(self, action, dest, opt, value, values, parser):
159 if action == "map":
160 values.ensure_value(dest, parser.map[opt.lstrip('-')])
161 elif action == "map_extend":
162 values.ensure_value(dest, []).extend(parser.map[opt.lstrip('-')])
163 else:
164 Option.take_action(self, action, dest, opt, value, values, parser)
167 ### SCRIPT HANDLING
170 # You may make a subclass of Script if you need additional script handling
171 # besides just a data representation. For instance, anaconda may subclass
172 # this to add a run method.
173 class Script:
174 def __repr__(self):
175 retval = ("(s: '%s' i: %s c: %d)") % \
176 (self.script, self.interp, self.inChroot)
177 return string.replace(retval, "\n", "|")
179 def __init__(self, script, interp = "/bin/sh", inChroot = False,
180 logfile = None, errorOnFail = False, type = KS_SCRIPT_PRE):
181 self.script = string.join(script, "")
182 self.interp = interp
183 self.inChroot = inChroot
184 self.logfile = logfile
185 self.errorOnFail = errorOnFail
186 self.type = type
188 # Produce a string representation of the script suitable for writing
189 # to a kickstart file. Add this to the end of the %whatever header.
190 def write(self):
191 retval = ""
192 if self.interp != "/bin/sh" and self.interp != "":
193 retval = retval + " --interp %s" % self.interp
194 if self.type == KS_SCRIPT_POST and not self.inChroot:
195 retval = retval + " --nochroot"
196 if self.logfile != None:
197 retval = retval + " --logfile %s" % self.logfile
198 if self.errorOnFail:
199 retval = retval + " --erroronfail"
201 retval = retval + "\n%s\n" % self.script
202 return retval
205 ### COMMAND HANDLERS
208 # You may make a subclass of KickstartHandlers if you need to do something
209 # besides just build up the data store. If you need to do additional processing
210 # just make a subclass, define handlers for each command in your subclass, and
211 # make sure to call the same handler in the super class before whatever you
212 # want to do. Also if you need to make a new parser that only takes action
213 # for a subset of commands, make a subclass and define all the handlers to
214 # None except the ones you care about.
215 class KickstartHandlers:
216 def __init__ (self, ksdata):
217 self.ksdata = ksdata
219 # These will get set by the handleCommand method in the parser.
220 self.lineno = 0
221 self.currentCmd = ""
223 self.handlers = { "auth" : self.doAuthconfig,
224 "authconfig" : self.doAuthconfig,
225 "autopart" : self.doAutoPart,
226 "autostep" : self.doAutoStep,
227 "bootloader" : self.doBootloader,
228 "cdrom" : self.doMethod,
229 "clearpart" : self.doClearPart,
230 "cmdline" : self.doDisplayMode,
231 "device" : self.doDevice,
232 "deviceprobe" : self.doDeviceProbe,
233 "driverdisk" : self.doDriverDisk,
234 "firewall" : self.doFirewall,
235 "firstboot" : self.doFirstboot,
236 "graphical" : self.doDisplayMode,
237 "halt" : self.doReboot,
238 "harddrive" : self.doMethod,
239 "ignoredisk" : self.doIgnoreDisk,
240 # implied by lack of "upgrade" command
241 "install" : None,
242 "interactive" : self.doInteractive,
243 "iscsi" : self.doIscsi,
244 "iscsiname" : self.doIscsiName,
245 "keyboard" : self.doKeyboard,
246 "lang" : self.doLang,
247 "langsupport" : self.doLangSupport,
248 "logvol" : self.doLogicalVolume,
249 "logging" : self.doLogging,
250 "mediacheck" : self.doMediaCheck,
251 "monitor" : self.doMonitor,
252 "mouse" : self.doMouse,
253 "network" : self.doNetwork,
254 "nfs" : self.doMethod,
255 "multipath" : self.doMultiPath,
256 "dmraid" : self.doDmRaid,
257 "part" : self.doPartition,
258 "partition" : self.doPartition,
259 "poweroff" : self.doReboot,
260 "raid" : self.doRaid,
261 "reboot" : self.doReboot,
262 "repo" : self.doRepo,
263 "rootpw" : self.doRootPw,
264 "selinux" : self.doSELinux,
265 "services" : self.doServices,
266 "shutdown" : self.doReboot,
267 "skipx" : self.doSkipX,
268 "text" : self.doDisplayMode,
269 "timezone" : self.doTimezone,
270 "url" : self.doMethod,
271 "user" : self.doUser,
272 "upgrade" : self.doUpgrade,
273 "vnc" : self.doVnc,
274 "volgroup" : self.doVolumeGroup,
275 "xconfig" : self.doXConfig,
276 "zerombr" : self.doZeroMbr,
277 "zfcp" : self.doZFCP,
280 def _setToDict(self, optParser, opts, dict):
281 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
282 dict[key] = getattr(opts, key)
284 def _setToObj(self, optParser, opts, obj):
285 for key in filter (lambda k: getattr(opts, k) != None, optParser.keys()):
286 setattr(obj, key, getattr(opts, key))
288 def resetHandlers (self):
289 for key in self.handlers.keys():
290 self.handlers[key] = None
292 def deprecatedCommand(self, cmd):
293 warnings.warn(_("Ignoring deprecated command on line %s: The %s command has been deprecated and no longer has any effect. It may be removed from future releases, which will result in a fatal error from kickstart. Please modify your kickstart file to remove this command.") % (self.lineno, cmd), DeprecationWarning)
295 def doAuthconfig(self, args):
296 self.ksdata.authconfig = string.join(args)
298 def doAutoPart(self, args):
299 if len(args) > 0:
300 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s does not take any arguments") % "autopart")
302 self.ksdata.autopart = True
304 def doAutoStep(self, args):
305 op = KSOptionParser(lineno=self.lineno)
306 op.add_option("--autoscreenshot", dest="autoscreenshot",
307 action="store_true", default=False)
309 (opts, extra) = op.parse_args(args=args)
310 self._setToDict(op, opts, self.ksdata.autostep)
312 def doBootloader(self, args):
313 def driveorder_cb (option, opt_str, value, parser):
314 for d in value.split(','):
315 parser.values.ensure_value(option.dest, []).append(d)
317 op = KSOptionParser(lineno=self.lineno)
318 op.add_option("--append", dest="appendLine")
319 op.add_option("--location", dest="location", type="choice",
320 default="mbr",
321 choices=["mbr", "partition", "none", "boot"])
322 op.add_option("--lba32", dest="forceLBA", action="store_true",
323 default=False)
324 op.add_option("--password", dest="password", default="")
325 op.add_option("--md5pass", dest="md5pass", default="")
326 op.add_option("--upgrade", dest="upgrade", action="store_true",
327 default=False)
328 op.add_option("--driveorder", dest="driveorder", action="callback",
329 callback=driveorder_cb, nargs=1, type="string")
331 (opts, extra) = op.parse_args(args=args)
332 self._setToDict(op, opts, self.ksdata.bootloader)
334 def doClearPart(self, args):
335 def drive_cb (option, opt_str, value, parser):
336 for d in value.split(','):
337 parser.values.ensure_value(option.dest, []).append(d)
339 op = KSOptionParser(lineno=self.lineno)
340 op.add_option("--all", dest="type", action="store_const",
341 const=CLEARPART_TYPE_ALL)
342 op.add_option("--drives", dest="drives", action="callback",
343 callback=drive_cb, nargs=1, type="string")
344 op.add_option("--initlabel", dest="initAll", action="store_true",
345 default=False)
346 op.add_option("--linux", dest="type", action="store_const",
347 const=CLEARPART_TYPE_LINUX)
348 op.add_option("--none", dest="type", action="store_const",
349 const=CLEARPART_TYPE_NONE)
351 (opts, extra) = op.parse_args(args=args)
352 self._setToDict(op, opts, self.ksdata.clearpart)
354 def doDevice(self, args):
355 self.ksdata.device = string.join(args)
357 def doDeviceProbe(self, args):
358 self.ksdata.deviceprobe = string.join(args)
360 def doDisplayMode(self, args):
361 if self.currentCmd == "cmdline":
362 self.ksdata.displayMode = DISPLAY_MODE_CMDLINE
363 elif self.currentCmd == "graphical":
364 self.ksdata.displayMode = DISPLAY_MODE_GRAPHICAL
365 elif self.currentCmd == "text":
366 self.ksdata.displayMode = DISPLAY_MODE_TEXT
368 def doDriverDisk(self, args):
369 self.ksdata.driverdisk = string.join(args)
371 def doFirewall(self, args):
372 def firewall_port_cb (option, opt_str, value, parser):
373 for p in value.split(","):
374 p = p.strip()
375 if p.find(":") == -1:
376 p = "%s:tcp" % p
377 parser.values.ensure_value(option.dest, []).append(p)
379 op = KSOptionParser(map={"ssh":["22:tcp"], "telnet":["23:tcp"],
380 "smtp":["25:tcp"], "http":["80:tcp", "443:tcp"],
381 "ftp":["21:tcp"]}, lineno=self.lineno)
383 op.add_option("--disable", "--disabled", dest="enabled",
384 action="store_false")
385 op.add_option("--enable", "--enabled", dest="enabled",
386 action="store_true", default=True)
387 op.add_option("--ftp", "--http", "--smtp", "--ssh", "--telnet",
388 dest="ports", action="map_extend")
389 op.add_option("--port", dest="ports", action="callback",
390 callback=firewall_port_cb, nargs=1, type="string")
391 op.add_option("--trust", dest="trusts", action="append")
393 (opts, extra) = op.parse_args(args=args)
394 self._setToDict(op, opts, self.ksdata.firewall)
396 def doFirstboot(self, args):
397 op = KSOptionParser(lineno=self.lineno)
398 op.add_option("--disable", "--disabled", dest="firstboot",
399 action="store_const", const=FIRSTBOOT_SKIP)
400 op.add_option("--enable", "--enabled", dest="firstboot",
401 action="store_const", const=FIRSTBOOT_DEFAULT)
402 op.add_option("--reconfig", dest="firstboot", action="store_const",
403 const=FIRSTBOOT_RECONFIG)
405 (opts, extra) = op.parse_args(args=args)
406 self.ksdata.firstboot = opts.firstboot
408 def doIgnoreDisk(self, args):
409 def drive_cb (option, opt_str, value, parser):
410 for d in value.split(','):
411 parser.values.ensure_value(option.dest, []).append(d)
413 op = KSOptionParser(lineno=self.lineno)
414 op.add_option("--drives", dest="drives", action="callback",
415 callback=drive_cb, nargs=1, type="string")
417 (opts, extra) = op.parse_args(args=args)
419 self.ksdata.ignoredisk = opts.drives
421 def doInteractive(self, args):
422 if len(args) > 0:
423 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s does not take any arguments") % "interactive")
425 self.ksdata.interactive = True
427 def doIscsi(self, args):
428 op = KSOptionParser(lineno=self.lineno)
429 op.add_option("--target", dest="ipaddr", action="store", type="string")
430 op.add_option("--ipaddr", dest="ipaddr", action="store", type="string")
431 op.add_option("--port", dest="port", action="store", type="string")
432 op.add_option("--user", dest="user", action="store", type="string")
433 op.add_option("--password", dest="password", action="store",
434 type="string")
436 (opts, extra) = op.parse_args(args=args)
437 if not opts.ipaddr:
438 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("IP Address of iSCSI target required"))
439 if len(extra) != 0:
440 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Unexpected arguments for iscsi command"))
442 dd = KickstartIscsiData()
443 self._setToObj(op, opts, dd)
444 self.ksdata.iscsi.append(dd)
446 def doIscsiName(self, args):
447 if len(args) > 1:
448 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s only takes one argument") % "iscsiname")
449 self.ksdata.iscsiname = args[0]
451 def doKeyboard(self, args):
452 if len(args) > 1:
453 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s only takes one argument") % "keyboard")
455 self.ksdata.keyboard = args[0]
457 def doLang(self, args):
458 if len(args) > 1:
459 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s only takes one argument") % "lang")
461 self.ksdata.lang = args[0]
463 def doLangSupport(self, args):
464 self.deprecatedCommand("langsupport")
466 def doLogicalVolume(self, args):
467 def lv_cb (option, opt_str, value, parser):
468 parser.values.format = False
469 parser.values.preexist = True
471 op = KSOptionParser(lineno=self.lineno)
472 op.add_option("--bytes-per-inode", dest="bytesPerInode", action="store",
473 type="int", nargs=1)
474 op.add_option("--fsoptions", dest="fsopts")
475 op.add_option("--fstype", dest="fstype")
476 op.add_option("--grow", dest="grow", action="store_true",
477 default=False)
478 op.add_option("--maxsize", dest="maxSizeMB", action="store", type="int",
479 nargs=1)
480 op.add_option("--name", dest="name", required=1)
481 op.add_option("--noformat", action="callback", callback=lv_cb,
482 dest="format", default=True, nargs=0)
483 op.add_option("--percent", dest="percent", action="store", type="int",
484 nargs=1)
485 op.add_option("--recommended", dest="recommended", action="store_true",
486 default=False)
487 op.add_option("--size", dest="size", action="store", type="int",
488 nargs=1)
489 op.add_option("--useexisting", dest="preexist", action="store_true",
490 default=False)
491 op.add_option("--vgname", dest="vgname", required=1)
493 (opts, extra) = op.parse_args(args=args)
495 if len(extra) == 0:
496 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Mount point required for %s") % "logvol")
498 lvd = KickstartLogVolData()
499 self._setToObj(op, opts, lvd)
500 lvd.mountpoint = extra[0]
501 self.ksdata.lvList.append(lvd)
503 def doLogging(self, args):
504 op = KSOptionParser(lineno=self.lineno)
505 op.add_option("--host")
506 op.add_option("--level", type="choice",
507 choices=["debug", "info", "warning", "error", "critical"])
508 op.add_option("--port")
510 (opts, extra) = op.parse_args(args=args)
511 self._setToDict(op, opts, self.ksdata.logging)
513 def doMediaCheck(self, args):
514 if len(args) > 0:
515 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s does not take any arguments") % "mediacheck")
517 self.ksdata.mediacheck = True
519 def doMethod(self, args):
520 op = KSOptionParser(lineno=self.lineno)
522 self.ksdata.method["method"] = self.currentCmd
524 if self.currentCmd == "cdrom":
525 return
526 elif self.currentCmd == "harddrive":
527 op.add_option("--partition", dest="partition", required=1)
528 op.add_option("--dir", dest="dir", required=1)
529 elif self.currentCmd == "nfs":
530 op.add_option("--server", dest="server", required=1)
531 op.add_option("--dir", dest="dir", required=1)
532 op.add_option("--opts", dest="opts", required=0)
533 elif self.currentCmd == "url":
534 op.add_option("--url", dest="url", required=1)
536 (opts, extra) = op.parse_args(args=args)
537 self._setToDict(op, opts, self.ksdata.method)
539 def doMonitor(self, args):
540 op = KSOptionParser(lineno=self.lineno)
541 op.add_option("--hsync", dest="hsync")
542 op.add_option("--monitor", dest="monitor")
543 op.add_option("--noprobe", dest="probe", action="store_false",
544 default=True)
545 op.add_option("--vsync", dest="vsync")
547 (opts, extra) = op.parse_args(args=args)
549 if extra:
550 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Unexpected arguments to %s command: %s") % ("monitor", extra))
552 self._setToDict(op, opts, self.ksdata.monitor)
554 def doMouse(self, args):
555 self.deprecatedCommand("mouse")
557 def doNetwork(self, args):
558 op = KSOptionParser(lineno=self.lineno)
559 op.add_option("--bootproto", dest="bootProto", default="dhcp",
560 choices=["dhcp", "bootp", "static"])
561 op.add_option("--class", dest="dhcpclass")
562 op.add_option("--device", dest="device")
563 op.add_option("--essid", dest="essid")
564 op.add_option("--ethtool", dest="ethtool")
565 op.add_option("--gateway", dest="gateway")
566 op.add_option("--hostname", dest="hostname")
567 op.add_option("--ip", dest="ip")
568 op.add_option("--mtu", dest="mtu")
569 op.add_option("--nameserver", dest="nameserver")
570 op.add_option("--netmask", dest="netmask")
571 op.add_option("--nodns", dest="nodns", action="store_true",
572 default=False)
573 op.add_option("--noipv4", dest="ipv4", action="store_false",
574 default=True)
575 op.add_option("--noipv6", dest="ipv6", action="store_false",
576 default=True)
577 op.add_option("--notksdevice", dest="notksdevice", action="store_true",
578 default=False)
579 op.add_option("--onboot", dest="onboot", action="store",
580 type="ksboolean")
581 op.add_option("--wepkey", dest="wepkey")
583 (opts, extra) = op.parse_args(args=args)
585 nd = KickstartNetworkData()
586 self._setToObj(op, opts, nd)
587 self.ksdata.network.append(nd)
589 def doMultiPath(self, args):
590 op = KSOptionParser(lineno=self.lineno)
591 op.add_option("--name", dest="name", action="store", type="string",
592 required=1)
593 op.add_option("--device", dest="device", action="store", type="string",
594 required=1)
595 op.add_option("--rule", dest="rule", action="store", type="string",
596 required=1)
598 (opts, extra) = op.parse_args(args=args)
600 dd = KickstartMpPathData()
601 self._setToObj(op, opts, dd)
602 dd.mpdev = dd.mpdev.split('/')[-1]
604 parent = None
605 for x in range(0, len(self.ksdata.mpaths)):
606 mpath = self.ksdata.mpaths[x]
607 for path in mpath.paths:
608 if path.device == dd.device:
609 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Device '%s' is already used in multipath '%s'") % (path.device, path.mpdev))
610 if mpath.name == dd.mpdev:
611 parent = x
613 if parent is None:
614 mpath = KickstartMultiPathData()
615 self.ksdata.mpaths.append(mpath)
616 else:
617 mpath = self.ksdata.mpaths[x]
619 mpath.paths.append(dd)
621 def doDmRaid(self, args):
622 op = KSOptionParser(lineno=self.lineno)
623 op.add_option("--name", dest="name", action="store", type="string",
624 required=1)
625 op.add_option("--dev", dest="devices", action="append", type="string",
626 required=1)
628 (opts, extra) = op.parse_args(args=args)
630 dd = KickstartDmRaidData()
631 self._setToObj(op, opts, dd)
632 dd.name = dd.name.split('/')[-1]
633 self.ksdata.dmraids.append(dd)
635 def doPartition(self, args):
636 def part_cb (option, opt_str, value, parser):
637 if value.startswith("/dev/"):
638 parser.values.ensure_value(option.dest, value[5:])
639 else:
640 parser.values.ensure_value(option.dest, value)
642 op = KSOptionParser(lineno=self.lineno)
643 op.add_option("--active", dest="active", action="store_true",
644 default=False)
645 op.add_option("--asprimary", dest="primOnly", action="store_true",
646 default=False)
647 op.add_option("--bytes-per-inode", dest="bytesPerInode", action="store",
648 type="int", nargs=1)
649 op.add_option("--end", dest="end", action="store", type="int",
650 nargs=1)
651 op.add_option("--fsoptions", dest="fsopts")
652 op.add_option("--fstype", "--type", dest="fstype")
653 op.add_option("--grow", dest="grow", action="store_true", default=False)
654 op.add_option("--label", dest="label")
655 op.add_option("--maxsize", dest="maxSizeMB", action="store", type="int",
656 nargs=1)
657 op.add_option("--noformat", dest="format", action="store_false",
658 default=True)
659 op.add_option("--onbiosdisk", dest="onbiosdisk")
660 op.add_option("--ondisk", "--ondrive", dest="disk")
661 op.add_option("--onpart", "--usepart", dest="onPart", action="callback",
662 callback=part_cb, nargs=1, type="string")
663 op.add_option("--recommended", dest="recommended", action="store_true",
664 default=False)
665 op.add_option("--size", dest="size", action="store", type="int",
666 nargs=1)
667 op.add_option("--start", dest="start", action="store", type="int",
668 nargs=1)
670 (opts, extra) = op.parse_args(args=args)
672 if len(extra) != 1:
673 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Mount point required for %s") % "partition")
675 pd = KickstartPartData()
676 self._setToObj(op, opts, pd)
677 pd.mountpoint = extra[0]
678 self.ksdata.partitions.append(pd)
680 def doReboot(self, args):
681 if self.currentCmd == "reboot":
682 self.ksdata.reboot["action"] = KS_REBOOT
683 else:
684 self.ksdata.reboot["action"] = KS_SHUTDOWN
686 op = KSOptionParser(lineno=self.lineno)
687 op.add_option("--eject", dest="eject", action="store_true",
688 default=False)
690 (opts, extra) = op.parse_args(args=args)
691 self._setToDict(op, opts, self.ksdata.reboot)
693 def doRepo(self, args):
694 op = KSOptionParser(lineno=self.lineno)
695 op.add_option("--name", dest="name", required=1)
696 op.add_option("--baseurl")
697 op.add_option("--mirrorlist")
699 (opts, extra) = op.parse_args(args=args)
701 # This is lame, but I can't think of a better way to make sure only
702 # one of these two is specified.
703 if opts.baseurl and opts.mirrorlist:
704 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Only one of --baseurl and --mirrorlist may be specified for repo command."))
706 if not opts.baseurl and not opts.mirrorlist:
707 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("One of --baseurl or --mirrorlist must be specified for repo command."))
709 rd = KickstartRepoData()
710 self._setToObj(op, opts, rd)
711 self.ksdata.repoList.append(rd)
713 def doRaid(self, args):
714 def raid_cb (option, opt_str, value, parser):
715 parser.values.format = False
716 parser.values.preexist = True
718 def device_cb (option, opt_str, value, parser):
719 if value[0:2] == "md":
720 parser.values.ensure_value(option.dest, value[2:])
721 else:
722 parser.values.ensure_value(option.dest, value)
724 def level_cb (option, opt_str, value, parser):
725 if value == "RAID0" or value == "0":
726 parser.values.ensure_value(option.dest, "RAID0")
727 elif value == "RAID1" or value == "1":
728 parser.values.ensure_value(option.dest, "RAID1")
729 elif value == "RAID5" or value == "5":
730 parser.values.ensure_value(option.dest, "RAID5")
731 elif value == "RAID6" or value == "6":
732 parser.values.ensure_value(option.dest, "RAID6")
734 op = KSOptionParser(lineno=self.lineno)
735 op.add_option("--bytes-per-inode", dest="bytesPerInode", action="store",
736 type="int", nargs=1)
737 op.add_option("--device", action="callback", callback=device_cb,
738 dest="device", type="string", nargs=1, required=1)
739 op.add_option("--fsoptions", dest="fsopts")
740 op.add_option("--fstype", dest="fstype")
741 op.add_option("--level", dest="level", action="callback",
742 callback=level_cb, type="string", nargs=1)
743 op.add_option("--noformat", action="callback", callback=raid_cb,
744 dest="format", default=True, nargs=0)
745 op.add_option("--spares", dest="spares", action="store", type="int",
746 nargs=1, default=0)
747 op.add_option("--useexisting", dest="preexist", action="store_true",
748 default=False)
750 (opts, extra) = op.parse_args(args=args)
752 if len(extra) == 0:
753 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Mount point required for %s") % "raid")
755 rd = KickstartRaidData()
756 self._setToObj(op, opts, rd)
758 # --device can't just take an int in the callback above, because it
759 # could be specificed as "mdX", which causes optparse to error when
760 # it runs int().
761 rd.device = int(rd.device)
762 rd.mountpoint = extra[0]
763 rd.members = extra[1:]
764 self.ksdata.raidList.append(rd)
766 def doRootPw(self, args):
767 op = KSOptionParser(lineno=self.lineno)
768 op.add_option("--iscrypted", dest="isCrypted", action="store_true",
769 default=False)
771 (opts, extra) = op.parse_args(args=args)
772 self._setToDict(op, opts, self.ksdata.rootpw)
774 if len(extra) != 1:
775 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("A single argument is expected for the %s command") % "rootpw")
777 self.ksdata.rootpw["password"] = extra[0]
779 def doSELinux(self, args):
780 op = KSOptionParser(lineno=self.lineno)
781 op.add_option("--disabled", dest="sel", action="store_const",
782 const=SELINUX_DISABLED)
783 op.add_option("--enforcing", dest="sel", action="store_const",
784 const=SELINUX_ENFORCING)
785 op.add_option("--permissive", dest="sel", action="store_const",
786 const=SELINUX_PERMISSIVE)
788 (opts, extra) = op.parse_args(args=args)
789 self.ksdata.selinux = opts.sel
791 def doServices(self, args):
792 def services_cb (option, opt_str, value, parser):
793 for d in value.split(','):
794 parser.values.ensure_value(option.dest, []).append(d)
796 op = KSOptionParser(lineno=self.lineno)
797 op.add_option("--disabled", dest="disabled", action="callback",
798 callback=services_cb, nargs=1, type="string")
799 op.add_option("--enabled", dest="enabled", action="callback",
800 callback=services_cb, nargs=1, type="string")
802 (opts, extra) = op.parse_args(args=args)
803 self._setToDict(op, opts, self.ksdata.services)
805 def doSkipX(self, args):
806 if len(args) > 0:
807 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s does not take any arguments") % "skipx")
809 self.ksdata.skipx = True
811 def doTimezone(self, args):
812 op = KSOptionParser(lineno=self.lineno)
813 op.add_option("--utc", "--isUtc", dest="isUtc", action="store_true", default=False)
815 (opts, extra) = op.parse_args(args=args)
816 self._setToDict(op, opts, self.ksdata.timezone)
818 if len(extra) != 1:
819 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("A single argument is expected for the %s command") % "timezone")
821 self.ksdata.timezone["timezone"] = extra[0]
823 def doUpgrade(self, args):
824 if len(args) > 0:
825 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Command %s does not take any arguments") % "upgrade")
827 self.ksdata.upgrade = True
829 def doUser(self, args):
830 def groups_cb (option, opt_str, value, parser):
831 for d in value.split(','):
832 parser.values.ensure_value(option.dest, []).append(d)
834 op = KSOptionParser(lineno=self.lineno)
835 op.add_option("--groups", dest="groups", action="callback",
836 callback=groups_cb, nargs=1, type="string")
837 op.add_option("--homedir")
838 op.add_option("--iscrypted", dest="isCrypted", action="store_true",
839 default=False)
840 op.add_option("--name", required=1)
841 op.add_option("--password")
842 op.add_option("--shell")
843 op.add_option("--uid", type="int")
845 (opts, extra) = op.parse_args(args=args)
847 user = KickstartUserData()
848 self._setToObj(op, opts, user)
849 self.ksdata.userList.append(user)
851 def doVnc(self, args):
852 def connect_cb (option, opt_str, value, parser):
853 cargs = value.split(":")
854 parser.values.ensure_value("host", cargs[0])
856 if len(cargs) > 1:
857 parser.values.ensure_value("port", cargs[1])
859 op = KSOptionParser(lineno=self.lineno)
860 op.add_option("--connect", action="callback", callback=connect_cb,
861 nargs=1, type="string", deprecated=1)
862 op.add_option("--password", dest="password")
863 op.add_option("--host", dest="host")
864 op.add_option("--port", dest="port")
866 self.ksdata.vnc["enabled"] = True
868 (opts, extra) = op.parse_args(args=args)
869 self._setToDict(op, opts, self.ksdata.vnc)
871 def doVolumeGroup(self, args):
872 # Have to be a little more complicated to set two values.
873 def vg_cb (option, opt_str, value, parser):
874 parser.values.format = False
875 parser.values.preexist = True
877 op = KSOptionParser(lineno=self.lineno)
878 op.add_option("--noformat", action="callback", callback=vg_cb,
879 dest="format", default=True, nargs=0)
880 op.add_option("--pesize", dest="pesize", type="int", nargs=1,
881 default=32768)
882 op.add_option("--useexisting", dest="preexist", action="store_true",
883 default=False)
885 (opts, extra) = op.parse_args(args=args)
887 vgd = KickstartVolGroupData()
888 self._setToObj(op, opts, vgd)
889 vgd.vgname = extra[0]
890 vgd.physvols = extra[1:]
891 self.ksdata.vgList.append(vgd)
893 def doXConfig(self, args):
894 op = KSOptionParser(lineno=self.lineno)
895 op.add_option("--card", deprecated=1)
896 op.add_option("--driver", dest="driver")
897 op.add_option("--defaultdesktop", dest="defaultdesktop")
898 op.add_option("--depth", dest="depth", action="store", type="int",
899 nargs=1)
900 op.add_option("--hsync", deprecated=1)
901 op.add_option("--monitor", deprecated=1)
902 op.add_option("--noprobe", deprecated=1)
903 op.add_option("--resolution", dest="resolution")
904 op.add_option("--startxonboot", dest="startX", action="store_true",
905 default=False)
906 op.add_option("--videoram", dest="videoRam")
907 op.add_option("--vsync", deprecated=1)
909 (opts, extra) = op.parse_args(args=args)
910 if extra:
911 raise KickstartValueError, formatErrorMsg(self.lineno, msg=_("Unexpected arguments to %s command: %s" % ("xconfig", extra)))
913 self._setToDict(op, opts, self.ksdata.xconfig)
915 def doZeroMbr(self, args):
916 if len(args) > 0:
917 warnings.warn(_("Ignoring deprecated option on line %s: The zerombr command no longer takes any options. In future releases, this will result in a fatal error from kickstart. Please modify your kickstart file to remove any options.") % self.lineno, DeprecationWarning)
919 self.ksdata.zerombr = True
921 def doZFCP(self, args):
922 op = KSOptionParser(lineno=self.lineno)
923 op.add_option("--devnum", dest="devnum", required=1)
924 op.add_option("--fcplun", dest="fcplun", required=1)
925 op.add_option("--scsiid", dest="scsiid")
926 op.add_option("--scsilun", dest="scsilun")
927 op.add_option("--wwpn", dest="wwpn", required=1)
929 (opts, extra) = op.parse_args(args=args)
931 dd = KickstartZFCPData()
932 self._setToObj(op, opts, dd)
933 self.ksdata.zfcp.append(dd)
936 ### PARSER
939 # The kickstart file parser. This only transitions between states and calls
940 # handlers at certain points. To create a specialized parser, make a subclass
941 # of this and override the methods you care about. Methods that don't need to
942 # do anything may just pass.
944 # Passing None for kshandlers is valid just in case you don't care about
945 # handling any commands.
946 class KickstartParser:
947 def __init__ (self, ksdata, kshandlers, followIncludes=True,
948 errorsAreFatal=True, missingIncludeIsFatal=True):
949 self.handler = kshandlers
950 self.ksdata = ksdata
951 self.followIncludes = followIncludes
952 self.missingIncludeIsFatal = missingIncludeIsFatal
953 self.state = STATE_COMMANDS
954 self.script = None
955 self.includeDepth = 0
956 self.errorsAreFatal = errorsAreFatal
958 # Functions to be called when we are at certain points in the
959 # kickstart file parsing. Override these if you need special
960 # behavior.
961 def addScript (self):
962 if string.join(self.script["body"]).strip() == "":
963 return
965 s = Script (self.script["body"], self.script["interp"],
966 self.script["chroot"], self.script["log"],
967 self.script["errorOnFail"], self.script["type"])
969 self.ksdata.scripts.append(s)
971 def addPackages (self, line):
972 stripped = line.strip()
974 if stripped[0] == '@':
975 self.ksdata.groupList.append(stripped[1:].lstrip())
976 elif stripped[0] == '-':
977 self.ksdata.excludedList.append(stripped[1:].lstrip())
978 else:
979 self.ksdata.packageList.append(stripped)
981 def handleCommand (self, lineno, args):
982 if not self.handler:
983 return
985 cmd = args[0]
986 cmdArgs = args[1:]
988 if not self.handler.handlers.has_key(cmd):
989 raise KickstartParseError, formatErrorMsg(lineno, msg=_("Unknown command: %s" % cmd))
990 else:
991 if self.handler.handlers[cmd] != None:
992 self.handler.currentCmd = cmd
993 self.handler.lineno = lineno
994 self.handler.handlers[cmd](cmdArgs)
996 def handlePackageHdr (self, lineno, args):
997 op = KSOptionParser(lineno=lineno)
998 op.add_option("--excludedocs", dest="excludedocs", action="store_true",
999 default=False)
1000 op.add_option("--ignoremissing", dest="ignoremissing",
1001 action="store_true", default=False)
1002 op.add_option("--nobase", dest="nobase", action="store_true",
1003 default=False)
1004 op.add_option("--ignoredeps", dest="resolveDeps", action="store_false",
1005 deprecated=1)
1006 op.add_option("--resolvedeps", dest="resolveDeps", action="store_true",
1007 deprecated=1)
1009 (opts, extra) = op.parse_args(args=args[1:])
1011 self.ksdata.excludeDocs = opts.excludedocs
1012 self.ksdata.addBase = not opts.nobase
1013 if opts.ignoremissing:
1014 self.ksdata.handleMissing = KS_MISSING_IGNORE
1015 else:
1016 self.ksdata.handleMissing = KS_MISSING_PROMPT
1018 def handleScriptHdr (self, lineno, args):
1019 op = KSOptionParser(lineno=lineno)
1020 op.add_option("--erroronfail", dest="errorOnFail", action="store_true",
1021 default=False)
1022 op.add_option("--interpreter", dest="interpreter", default="/bin/sh")
1023 op.add_option("--log", "--logfile", dest="log")
1025 if args[0] == "%pre" or args[0] == "%traceback":
1026 self.script["chroot"] = False
1027 elif args[0] == "%post":
1028 self.script["chroot"] = True
1029 op.add_option("--nochroot", dest="nochroot", action="store_true",
1030 default=False)
1032 (opts, extra) = op.parse_args(args=args[1:])
1034 self.script["interp"] = opts.interpreter
1035 self.script["log"] = opts.log
1036 self.script["errorOnFail"] = opts.errorOnFail
1037 if hasattr(opts, "nochroot"):
1038 self.script["chroot"] = not opts.nochroot
1040 # Parser state machine. Don't override this in a subclass.
1041 def readKickstart (self, file):
1042 # For error reporting.
1043 lineno = 0
1045 fh = open(file)
1046 needLine = True
1048 while True:
1049 if needLine:
1050 line = fh.readline()
1051 lineno += 1
1052 needLine = False
1054 # At the end of an included file
1055 if line == "" and self.includeDepth > 0:
1056 fh.close()
1057 break
1059 # Don't eliminate whitespace or comments from scripts.
1060 if line.isspace() or (line != "" and line.lstrip()[0] == '#'):
1061 # Save the platform for s-c-kickstart, though.
1062 if line[:10] == "#platform=" and self.state == STATE_COMMANDS:
1063 self.ksdata.platform = line[11:]
1065 if self.state in [STATE_PRE, STATE_POST, STATE_TRACEBACK]:
1066 self.script["body"].append(line)
1068 needLine = True
1069 continue
1071 # We only want to split the line if we're outside of a script,
1072 # as inside the script might involve some pretty weird quoting
1073 # that shlex doesn't understand.
1074 if self.state in [STATE_PRE, STATE_POST, STATE_TRACEBACK]:
1075 # Have we found a state transition? If so, we still want
1076 # to split. Otherwise, args won't be set but we'll fall through
1077 # all the way to the last case.
1078 if line != "" and string.split(line.lstrip())[0] in \
1079 ["%post", "%pre", "%traceback", "%include", "%packages"]:
1080 args = shlex.split(line)
1081 else:
1082 args = None
1083 else:
1084 args = shlex.split(line)
1086 if args and args[0] == "%include":
1087 # This case comes up primarily in ksvalidator.
1088 if not self.followIncludes:
1089 needLine = True
1090 continue
1092 if not args[1]:
1093 raise KickstartParseError, formatErrorMsg(lineno)
1094 else:
1095 self.includeDepth += 1
1097 try:
1098 self.readKickstart (args[1])
1099 except IOError:
1100 # Handle the include file being provided over the
1101 # network in a %pre script. This case comes up in the
1102 # early parsing in anaconda.
1103 if self.missingIncludeIsFatal:
1104 raise
1106 self.includeDepth -= 1
1107 needLine = True
1108 continue
1110 if self.state == STATE_COMMANDS:
1111 if not args and self.includeDepth == 0:
1112 self.state = STATE_END
1113 elif args[0] in ["%pre", "%post", "%traceback"]:
1114 self.state = STATE_SCRIPT_HDR
1115 elif args[0] == "%packages":
1116 self.state = STATE_PACKAGES
1117 elif args[0][0] == '%':
1118 # This error is too difficult to continue from, without
1119 # lots of resync code. So just print this one and quit.
1120 raise KickstartParseError, formatErrorMsg(lineno)
1121 else:
1122 needLine = True
1124 if self.errorsAreFatal:
1125 self.handleCommand(lineno, args)
1126 else:
1127 try:
1128 self.handleCommand(lineno, args)
1129 except Exception, msg:
1130 print msg
1132 elif self.state == STATE_PACKAGES:
1133 if not args and self.includeDepth == 0:
1134 self.state = STATE_END
1135 elif args[0] in ["%pre", "%post", "%traceback"]:
1136 self.state = STATE_SCRIPT_HDR
1137 elif args[0] == "%packages":
1138 needLine = True
1140 if self.errorsAreFatal:
1141 self.handlePackageHdr (lineno, args)
1142 else:
1143 try:
1144 self.handlePackageHdr (lineno, args)
1145 except Exception, msg:
1146 print msg
1147 elif args[0][0] == '%':
1148 # This error is too difficult to continue from, without
1149 # lots of resync code. So just print this one and quit.
1150 raise KickstartParseError, formatErrorMsg(lineno)
1151 else:
1152 needLine = True
1153 self.addPackages (string.rstrip(line))
1155 elif self.state == STATE_SCRIPT_HDR:
1156 needLine = True
1157 self.script = {"body": [], "interp": "/bin/sh", "log": None,
1158 "errorOnFail": False}
1160 if not args and self.includeDepth == 0:
1161 self.state = STATE_END
1162 elif args[0] == "%pre":
1163 self.state = STATE_PRE
1164 self.script["type"] = KS_SCRIPT_PRE
1165 elif args[0] == "%post":
1166 self.state = STATE_POST
1167 self.script["type"] = KS_SCRIPT_POST
1168 elif args[0] == "%traceback":
1169 self.state = STATE_TRACEBACK
1170 self.script["type"] = KS_SCRIPT_TRACEBACK
1171 elif args[0][0] == '%':
1172 # This error is too difficult to continue from, without
1173 # lots of resync code. So just print this one and quit.
1174 raise KickstartParseError, formatErrorMsg(lineno)
1176 if self.errorsAreFatal:
1177 self.handleScriptHdr (lineno, args)
1178 else:
1179 try:
1180 self.handleScriptHdr (lineno, args)
1181 except Exception, msg:
1182 print msg
1184 elif self.state in [STATE_PRE, STATE_POST, STATE_TRACEBACK]:
1185 if line == "" and self.includeDepth == 0:
1186 # If we're at the end of the kickstart file, add the script.
1187 self.addScript()
1188 self.state = STATE_END
1189 elif args and args[0] in ["%pre", "%post", "%traceback", "%packages"]:
1190 # Otherwise we're now at the start of the next section.
1191 # Figure out what kind of a script we just finished
1192 # reading, add it to the list, and switch to the initial
1193 # state.
1194 self.addScript()
1195 self.state = STATE_COMMANDS
1196 else:
1197 # Otherwise just add to the current script body.
1198 self.script["body"].append(line)
1199 needLine = True
1201 elif self.state == STATE_END:
1202 break