Add support for timeout and totaltimeout to livecd-iso-to-disk (#531566)
[livecd.git] / imgcreate / kickstart.py
blob9a2145a9062a377712a887251e06c375b6b3b743
2 # kickstart.py : Apply kickstart configuration to a system
4 # Copyright 2007, Red Hat Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 import os
20 import os.path
21 import shutil
22 import subprocess
23 import time
24 import logging
25 import urlgrabber
27 try:
28 import system_config_keyboard.keyboard as keyboard
29 except ImportError:
30 import rhpl.keyboard as keyboard
32 import pykickstart.commands as kscommands
33 import pykickstart.constants as ksconstants
34 import pykickstart.errors as kserrors
35 import pykickstart.parser as ksparser
36 import pykickstart.version as ksversion
38 import imgcreate.errors as errors
39 import imgcreate.fs as fs
41 def read_kickstart(path):
42 """Parse a kickstart file and return a KickstartParser instance.
44 This is a simple utility function which takes a path to a kickstart file,
45 parses it and returns a pykickstart KickstartParser instance which can
46 be then passed to an ImageCreator constructor.
48 If an error occurs, a CreatorError exception is thrown.
50 """
51 version = ksversion.makeVersion()
52 ks = ksparser.KickstartParser(version)
53 try:
54 ksfile = urlgrabber.urlgrab(path)
55 ks.readKickstart(ksfile)
56 # Fallback to e.args[0] is a workaround for bugs in urlgragger and pykickstart.
57 except IOError, e:
58 raise errors.KickstartError("Failed to read kickstart file "
59 "'%s' : %s" % (path, e.strerror or
60 e.args[0]))
61 except kserrors.KickstartError, e:
62 raise errors.KickstartError("Failed to parse kickstart file "
63 "'%s' : %s" % (path, e))
64 return ks
66 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
67 """Construct and return an image name string.
69 This is a utility function to help create sensible name and fslabel
70 strings. The name is constructed using the sans-prefix-and-extension
71 kickstart filename and the supplied prefix and suffix.
73 If the name exceeds the maxlen length supplied, the prefix is first dropped
74 and then the kickstart filename portion is reduced until it fits. In other
75 words, the suffix takes precedence over the kickstart portion and the
76 kickstart portion takes precedence over the prefix.
78 kscfg -- a path to a kickstart file
79 prefix -- a prefix to prepend to the name; defaults to None, which causes
80 no prefix to be used
81 suffix -- a suffix to append to the name; defaults to None, which causes
82 a YYYYMMDDHHMM suffix to be used
83 maxlen -- the maximum length for the returned string; defaults to None,
84 which means there is no restriction on the name length
86 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
88 """
89 name = os.path.basename(kscfg)
90 idx = name.rfind('.')
91 if idx >= 0:
92 name = name[:idx]
94 if prefix is None:
95 prefix = ""
96 if suffix is None:
97 suffix = time.strftime("%Y%m%d%H%M")
99 if name.startswith(prefix):
100 name = name[len(prefix):]
102 ret = prefix + name + "-" + suffix
103 if not maxlen is None and len(ret) > maxlen:
104 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
106 return ret
108 class KickstartConfig(object):
109 """A base class for applying kickstart configurations to a system."""
110 def __init__(self, instroot):
111 self.instroot = instroot
113 def path(self, subpath):
114 return self.instroot + subpath
116 def chroot(self):
117 os.chroot(self.instroot)
118 os.chdir("/")
120 def call(self, args):
121 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
122 raise errors.KickstartError("Unable to run %s!" %(args))
123 subprocess.call(args, preexec_fn = self.chroot)
125 def apply(self):
126 pass
128 class LanguageConfig(KickstartConfig):
129 """A class to apply a kickstart language configuration to a system."""
130 def apply(self, kslang):
131 lang = kslang.lang or "en_US.UTF-8"
133 f = open(self.path("/etc/sysconfig/i18n"), "w+")
134 f.write("LANG=\"" + lang + "\"\n")
135 f.close()
137 class KeyboardConfig(KickstartConfig):
138 """A class to apply a kickstart keyboard configuration to a system."""
139 def apply(self, kskeyboard):
140 k = keyboard.Keyboard()
141 if kskeyboard.keyboard:
142 k.set(kskeyboard.keyboard)
143 k.write(self.instroot)
145 class TimezoneConfig(KickstartConfig):
146 """A class to apply a kickstart timezone configuration to a system."""
147 def apply(self, kstimezone):
148 tz = kstimezone.timezone or "America/New_York"
149 utc = str(kstimezone.isUtc)
151 f = open(self.path("/etc/sysconfig/clock"), "w+")
152 f.write("ZONE=\"" + tz + "\"\n")
153 f.write("UTC=" + utc + "\n")
154 f.close()
155 try:
156 shutil.copyfile(self.path("/usr/share/zoneinfo/%s" %(tz,)),
157 self.path("/etc/localtime"))
158 except OSError, e:
159 log.error("Error copying timezone: %s" %(e.strerror,))
162 class AuthConfig(KickstartConfig):
163 """A class to apply a kickstart authconfig configuration to a system."""
164 def apply(self, ksauthconfig):
165 if not os.path.exists(self.path("/usr/sbin/authconfig")):
166 return
168 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
169 args = ["/usr/sbin/authconfig", "--update", "--nostart"]
170 self.call(args + auth.split())
172 class FirewallConfig(KickstartConfig):
173 """A class to apply a kickstart firewall configuration to a system."""
174 def apply(self, ksfirewall):
175 if not os.path.exists(self.path("/usr/sbin/lokkit")):
176 return
177 args = ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
178 if ksfirewall.enabled:
179 args.append("--enabled")
181 for port in ksfirewall.ports:
182 args.append("--port=%s" %(port,))
183 for svc in ksfirewall.services:
184 args.append("--service=%s" %(svc,))
185 for dev in ksfirewall.trusts:
186 args.append("--trust=%s" %(dev,))
187 else:
188 args.append("--disabled")
190 self.call(args)
192 class RootPasswordConfig(KickstartConfig):
193 """A class to apply a kickstart root password configuration to a system."""
194 def unset(self):
195 self.call(["/usr/bin/passwd", "-d", "root"])
197 def set_encrypted(self, password):
198 self.call(["/usr/sbin/usermod", "-p", password, "root"])
200 def set_unencrypted(self, password):
201 for p in ("/bin/echo", "/usr/bin/passwd"):
202 if not os.path.exists("%s/%s" %(self.instroot, p)):
203 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
205 p1 = subprocess.Popen(["/bin/echo", password],
206 stdout = subprocess.PIPE,
207 preexec_fn = self.chroot)
208 p2 = subprocess.Popen(["/usr/bin/passwd", "--stdin", "root"],
209 stdin = p1.stdout,
210 stdout = subprocess.PIPE,
211 preexec_fn = self.chroot)
212 p2.communicate()
214 def apply(self, ksrootpw):
215 if ksrootpw.isCrypted:
216 self.set_encrypted(ksrootpw.password)
217 elif ksrootpw.password != "":
218 self.set_unencrypted(ksrootpw.password)
219 else:
220 self.unset()
222 class ServicesConfig(KickstartConfig):
223 """A class to apply a kickstart services configuration to a system."""
224 def apply(self, ksservices):
225 if not os.path.exists(self.path("/sbin/chkconfig")):
226 return
227 for s in ksservices.enabled:
228 self.call(["/sbin/chkconfig", s, "on"])
229 for s in ksservices.disabled:
230 self.call(["/sbin/chkconfig", s, "off"])
232 class XConfig(KickstartConfig):
233 """A class to apply a kickstart X configuration to a system."""
234 def apply(self, ksxconfig):
235 if ksxconfig.startX:
236 f = open(self.path("/etc/inittab"), "rw+")
237 buf = f.read()
238 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
239 f.seek(0)
240 f.write(buf)
241 f.close()
242 if ksxconfig.defaultdesktop:
243 f = open(self.path("/etc/sysconfig/desktop"), "w")
244 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
245 f.close()
247 class RPMMacroConfig(KickstartConfig):
248 """A class to apply the specified rpm macros to the filesystem"""
249 def apply(self, ks):
250 if not ks:
251 return
252 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
253 if exclude_docs(ks):
254 f.write("%_excludedocs 1\n")
255 if not selinux_enabled(ks):
256 f.write("%__file_context_path %{nil}\n")
257 if inst_langs(ks) != None:
258 f.write("%_install_langs ")
259 f.write(inst_langs(ks))
260 f.write("\n")
261 f.close()
263 class NetworkConfig(KickstartConfig):
264 """A class to apply a kickstart network configuration to a system."""
265 def write_ifcfg(self, network):
266 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
268 f = file(p, "w+")
269 os.chmod(p, 0644)
271 f.write("DEVICE=%s\n" % network.device)
272 f.write("BOOTPROTO=%s\n" % network.bootProto)
274 if network.bootProto.lower() == "static":
275 if network.ip:
276 f.write("IPADDR=%s\n" % network.ip)
277 if network.netmask:
278 f.write("NETMASK=%s\n" % network.netmask)
280 if network.onboot:
281 f.write("ONBOOT=on\n")
282 else:
283 f.write("ONBOOT=off\n")
285 if network.essid:
286 f.write("ESSID=%s\n" % network.essid)
288 if network.ethtool:
289 if network.ethtool.find("autoneg") == -1:
290 network.ethtool = "autoneg off " + network.ethtool
291 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
293 if network.bootProto.lower() == "dhcp":
294 if network.hostname:
295 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
296 if network.dhcpclass:
297 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
299 if network.mtu:
300 f.write("MTU=%s\n" % network.mtu)
302 f.close()
304 def write_wepkey(self, network):
305 if not network.wepkey:
306 return
308 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
309 f = file(p, "w+")
310 os.chmod(p, 0600)
311 f.write("KEY=%s\n" % network.wepkey)
312 f.close()
314 def write_sysconfig(self, useipv6, hostname, gateway):
315 path = self.path("/etc/sysconfig/network")
316 f = file(path, "w+")
317 os.chmod(path, 0644)
319 f.write("NETWORKING=yes\n")
321 if useipv6:
322 f.write("NETWORKING_IPV6=yes\n")
323 else:
324 f.write("NETWORKING_IPV6=no\n")
326 if hostname:
327 f.write("HOSTNAME=%s\n" % hostname)
328 else:
329 f.write("HOSTNAME=localhost.localdomain\n")
331 if gateway:
332 f.write("GATEWAY=%s\n" % gateway)
334 f.close()
336 def write_hosts(self, hostname):
337 localline = ""
338 if hostname and hostname != "localhost.localdomain":
339 localline += hostname + " "
340 l = hostname.split(".")
341 if len(l) > 1:
342 localline += l[0] + " "
343 localline += "localhost.localdomain localhost"
345 path = self.path("/etc/hosts")
346 f = file(path, "w+")
347 os.chmod(path, 0644)
348 f.write("127.0.0.1\t\t%s\n" % localline)
349 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
350 f.close()
352 def write_resolv(self, nodns, nameservers):
353 if nodns or not nameservers:
354 return
356 path = self.path("/etc/resolv.conf")
357 f = file(path, "w+")
358 os.chmod(path, 0644)
360 for ns in (nameservers):
361 if ns:
362 f.write("nameserver %s\n" % ns)
364 f.close()
366 def apply(self, ksnet):
367 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
369 useipv6 = False
370 nodns = False
371 hostname = None
372 gateway = None
373 nameservers = None
375 for network in ksnet.network:
376 if not network.device:
377 raise errors.KickstartError("No --device specified with "
378 "network kickstart command")
380 if (network.onboot and network.bootProto.lower() != "dhcp" and
381 not (network.ip and network.netmask)):
382 raise errors.KickstartError("No IP address and/or netmask "
383 "specified with static "
384 "configuration for '%s'" %
385 network.device)
387 self.write_ifcfg(network)
388 self.write_wepkey(network)
390 if network.ipv6:
391 useipv6 = True
392 if network.nodns:
393 nodns = True
395 if network.hostname:
396 hostname = network.hostname
397 if network.gateway:
398 gateway = network.gateway
400 if network.nameserver:
401 nameservers = network.nameserver.split(",")
403 self.write_sysconfig(useipv6, hostname, gateway)
404 self.write_hosts(hostname)
405 self.write_resolv(nodns, nameservers)
407 class SelinuxConfig(KickstartConfig):
408 """A class to apply a kickstart selinux configuration to a system."""
409 def relabel(self, ksselinux):
410 # touch some files which get unhappy if they're not labeled correctly
411 for fn in ("/etc/resolv.conf",):
412 path = self.path(fn)
413 f = file(path, "w+")
414 os.chmod(path, 0644)
415 f.close()
417 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
418 return
420 if not os.path.exists(self.path("/sbin/setfiles")):
421 return
423 self.call(["/sbin/setfiles", "-e", "/proc", "-e", "/sys", "-e", "/dev", "-e", "/selinux", "/etc/selinux/targeted/contexts/files/file_contexts", "/"])
425 def apply(self, ksselinux):
426 if os.path.exists(self.path("/usr/sbin/lokkit")):
427 args = ["/usr/sbin/lokkit", "-f", "--quiet", "--nostart"]
429 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
430 args.append("--selinux=enforcing")
431 if ksselinux.selinux == ksconstants.SELINUX_PERMISSIVE:
432 args.append("--selinux=permissive")
433 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
434 args.append("--selinux=disabled")
436 self.call(args)
438 self.relabel(ksselinux)
440 def get_image_size(ks, default = None):
441 __size = 0
442 for p in ks.handler.partition.partitions:
443 if p.mountpoint == "/" and p.size:
444 __size = p.size
445 if __size > 0:
446 return int(__size) * 1024L * 1024L
447 else:
448 return default
450 def get_image_fstype(ks, default = None):
451 for p in ks.handler.partition.partitions:
452 if p.mountpoint == "/" and p.fstype:
453 return p.fstype
454 return default
456 def get_modules(ks):
457 devices = []
458 if not hasattr(ks.handler.device, "deviceList"):
459 devices.append(ks.handler.device)
460 else:
461 devices.extend(ks.handler.device.deviceList)
463 modules = []
464 for device in devices:
465 if not device.moduleName:
466 continue
467 modules.extend(device.moduleName.split(":"))
469 return modules
471 def get_timeout(ks, default = None):
472 if not hasattr(ks.handler.bootloader, "timeout"):
473 return default
474 if ks.handler.bootloader.timeout is None:
475 return default
476 return int(ks.handler.bootloader.timeout)
478 def get_kernel_args(ks, default = "ro liveimg quiet"):
479 if not hasattr(ks.handler.bootloader, "appendLine"):
480 return default
481 if ks.handler.bootloader.appendLine is None:
482 return default
483 return "%s %s" %(default, ks.handler.bootloader.appendLine)
485 def get_default_kernel(ks, default = None):
486 if not hasattr(ks.handler.bootloader, "default"):
487 return default
488 if not ks.handler.bootloader.default:
489 return default
490 return ks.handler.bootloader.default
492 def get_repos(ks, repo_urls = {}):
493 repos = {}
494 for repo in ks.handler.repo.repoList:
495 inc = []
496 if hasattr(repo, "includepkgs"):
497 inc.extend(repo.includepkgs)
499 exc = []
500 if hasattr(repo, "excludepkgs"):
501 exc.extend(repo.excludepkgs)
503 baseurl = repo.baseurl
504 mirrorlist = repo.mirrorlist
506 if repo.name in repo_urls:
507 baseurl = repo_urls[repo.name]
508 mirrorlist = None
510 if repos.has_key(repo.name):
511 logging.warn("Overriding already specified repo %s" %(repo.name,))
512 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc)
514 return repos.values()
516 def convert_method_to_repo(ks):
517 try:
518 ks.handler.repo.methodToRepo()
519 except (AttributeError, kserrors.KickstartError):
520 pass
522 def get_packages(ks, required = []):
523 return ks.handler.packages.packageList + required
525 def get_groups(ks, required = []):
526 return ks.handler.packages.groupList + required
528 def get_excluded(ks, required = []):
529 return ks.handler.packages.excludedList + required
531 def get_partitions(ks, required = []):
532 return ks.handler.partition.partitions
534 def ignore_missing(ks):
535 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
537 def exclude_docs(ks):
538 return ks.handler.packages.excludeDocs
540 def inst_langs(ks):
541 if hasattr(ks.handler.packages, "instLange"):
542 return ks.handler.packages.instLange
543 elif hasattr(ks.handler.packages, "instLangs"):
544 return ks.handler.packages.instLangs
545 return ""
547 def get_post_scripts(ks):
548 scripts = []
549 for s in ks.handler.scripts:
550 if s.type != ksparser.KS_SCRIPT_POST:
551 continue
552 scripts.append(s)
553 return scripts
555 def selinux_enabled(ks):
556 return ks.handler.selinux.selinux == ksconstants.SELINUX_ENFORCING