Ignore case when looking for UEFI boot*efi file (#1156380)
[livecd.git] / imgcreate / kickstart.py
blobaef3ef23d157d6abd355d645c67febe448b95ff7
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
26 import selinux
28 try:
29 import system_config_keyboard.keyboard as keyboard
30 except ImportError:
31 import rhpl.keyboard as keyboard
33 import pykickstart.commands as kscommands
34 import pykickstart.constants as ksconstants
35 import pykickstart.errors as kserrors
36 import pykickstart.parser as ksparser
37 import pykickstart.version as ksversion
39 import imgcreate.errors as errors
40 import imgcreate.fs as fs
42 def read_kickstart(path):
43 """Parse a kickstart file and return a KickstartParser instance.
45 This is a simple utility function which takes a path to a kickstart file,
46 parses it and returns a pykickstart KickstartParser instance which can
47 be then passed to an ImageCreator constructor.
49 If an error occurs, a CreatorError exception is thrown.
51 """
52 version = ksversion.makeVersion()
53 ks = ksparser.KickstartParser(version)
54 try:
55 ksfile = urlgrabber.urlgrab(path)
56 ks.readKickstart(ksfile)
57 # Fallback to e.args[0] is a workaround for bugs in urlgragger and pykickstart.
58 except IOError, e:
59 raise errors.KickstartError("Failed to read kickstart file "
60 "'%s' : %s" % (path, e.strerror or
61 e.args[0]))
62 except kserrors.KickstartError, e:
63 raise errors.KickstartError("Failed to parse kickstart file "
64 "'%s' : %s" % (path, e))
65 return ks
67 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
68 """Construct and return an image name string.
70 This is a utility function to help create sensible name and fslabel
71 strings. The name is constructed using the sans-prefix-and-extension
72 kickstart filename and the supplied prefix and suffix.
74 If the name exceeds the maxlen length supplied, the prefix is first dropped
75 and then the kickstart filename portion is reduced until it fits. In other
76 words, the suffix takes precedence over the kickstart portion and the
77 kickstart portion takes precedence over the prefix.
79 kscfg -- a path to a kickstart file
80 prefix -- a prefix to prepend to the name; defaults to None, which causes
81 no prefix to be used
82 suffix -- a suffix to append to the name; defaults to None, which causes
83 a YYYYMMDDHHMM suffix to be used
84 maxlen -- the maximum length for the returned string; defaults to None,
85 which means there is no restriction on the name length
87 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
89 """
90 name = os.path.basename(kscfg)
91 idx = name.rfind('.')
92 if idx >= 0:
93 name = name[:idx]
95 if prefix is None:
96 prefix = ""
97 if suffix is None:
98 suffix = time.strftime("%Y%m%d%H%M")
100 if name.startswith(prefix):
101 name = name[len(prefix):]
103 ret = prefix + name + "-" + suffix
104 if not maxlen is None and len(ret) > maxlen:
105 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
107 return ret
109 class KickstartConfig(object):
110 """A base class for applying kickstart configurations to a system."""
111 def __init__(self, instroot):
112 self.instroot = instroot
114 def path(self, subpath):
115 return self.instroot + subpath
117 def chroot(self):
118 os.chroot(self.instroot)
119 os.chdir("/")
121 def call(self, args):
122 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
123 raise errors.KickstartError("Unable to run %s!" %(args))
124 return subprocess.call(args, preexec_fn = self.chroot)
126 def apply(self):
127 pass
129 class LanguageConfig(KickstartConfig):
130 """A class to apply a kickstart language configuration to a system."""
131 def apply(self, kslang):
132 lang = kslang.lang or "en_US.UTF-8"
134 f = open(self.path("/etc/locale.conf"), "w+")
135 f.write("LANG=\"" + lang + "\"\n")
136 f.close()
138 class KeyboardConfig(KickstartConfig):
139 """A class to apply a kickstart keyboard configuration to a system."""
140 def apply(self, kskeyboard):
141 vcconf_file = self.path("/etc/vconsole.conf")
142 DEFAULT_VC_FONT = "latarcyrheb-sun16"
144 if not kskeyboard.keyboard:
145 kskeyboard.keyboard = "us"
147 try:
148 with open(vcconf_file, "w") as f:
149 f.write('KEYMAP="%s"\n' % kskeyboard.keyboard)
151 # systemd now defaults to a font that cannot display non-ascii
152 # characters, so we have to tell it to use a better one
153 f.write('FONT="%s"\n' % DEFAULT_VC_FONT)
154 except IOError as e:
155 logging.error("Cannot write vconsole configuration file: %s" % e)
157 class TimezoneConfig(KickstartConfig):
158 """A class to apply a kickstart timezone configuration to a system."""
159 def apply(self, kstimezone):
160 tz = kstimezone.timezone or "America/New_York"
161 utc = str(kstimezone.isUtc)
163 # /etc/localtime is a symlink with glibc > 2.15-41
164 # but if it exists as a file keep it as a file and fall back
165 # to a symlink.
166 localtime = self.path("/etc/localtime")
167 if os.path.isfile(localtime) and \
168 not os.path.islink(localtime):
169 try:
170 shutil.copy2(self.path("/usr/share/zoneinfo/%s" %(tz,)),
171 localtime)
172 except (OSError, shutil.Error) as e:
173 logging.error("Error copying timezone: %s" %(e.strerror,))
174 else:
175 if os.path.exists(localtime):
176 os.unlink(localtime)
177 os.symlink("/usr/share/zoneinfo/%s" %(tz,), localtime)
179 class AuthConfig(KickstartConfig):
180 """A class to apply a kickstart authconfig configuration to a system."""
181 def apply(self, ksauthconfig):
182 if not os.path.exists(self.path("/usr/sbin/authconfig")):
183 return
185 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
186 args = ["/usr/sbin/authconfig", "--update", "--nostart"]
187 self.call(args + auth.split())
189 class FirewallConfig(KickstartConfig):
190 """A class to apply a kickstart firewall configuration to a system."""
191 def apply(self, ksfirewall):
192 args = ["/usr/bin/firewall-offline-cmd"]
193 # enabled is None if neither --enable or --disable is passed
194 # default to enabled if nothing has been set.
195 if ksfirewall.enabled == False:
196 args += ["--disabled"]
197 else:
198 args += ["--enabled"]
200 for dev in ksfirewall.trusts:
201 args += [ "--trust=%s" % (dev,) ]
203 for port in ksfirewall.ports:
204 args += [ "--port=%s" % (port,) ]
206 for service in ksfirewall.services:
207 args += [ "--service=%s" % (service,) ]
209 self.call(args)
211 class RootPasswordConfig(KickstartConfig):
212 """A class to apply a kickstart root password configuration to a system."""
213 def lock(self):
214 self.call(["/usr/bin/passwd", "-l", "root"])
216 def set_encrypted(self, password):
217 self.call(["/usr/sbin/usermod", "-p", password, "root"])
219 def set_unencrypted(self, password):
220 for p in ("/bin/echo", "/usr/bin/passwd"):
221 if not os.path.exists("%s/%s" %(self.instroot, p)):
222 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
224 p1 = subprocess.Popen(["/bin/echo", password],
225 stdout = subprocess.PIPE,
226 preexec_fn = self.chroot)
227 p2 = subprocess.Popen(["/usr/bin/passwd", "--stdin", "root"],
228 stdin = p1.stdout,
229 stdout = subprocess.PIPE,
230 preexec_fn = self.chroot)
231 p2.communicate()
233 def apply(self, ksrootpw):
234 if ksrootpw.isCrypted:
235 self.set_encrypted(ksrootpw.password)
236 elif ksrootpw.password != "":
237 self.set_unencrypted(ksrootpw.password)
239 if ksrootpw.lock:
240 self.lock()
242 class ServicesConfig(KickstartConfig):
243 """A class to apply a kickstart services configuration to a system."""
244 def apply(self, ksservices):
245 if not os.path.exists(self.path("/sbin/chkconfig")):
246 return
247 for s in ksservices.enabled:
248 self.call(["/sbin/chkconfig", s, "on"])
249 for s in ksservices.disabled:
250 self.call(["/sbin/chkconfig", s, "off"])
252 class XConfig(KickstartConfig):
253 """A class to apply a kickstart X configuration to a system."""
254 RUNLEVELS = {3: 'multi-user.target', 5: 'graphical.target'}
256 def apply(self, ksxconfig):
257 if ksxconfig.defaultdesktop:
258 f = open(self.path("/etc/sysconfig/desktop"), "w")
259 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
260 f.close()
262 if ksxconfig.startX:
263 if not os.path.isdir(self.path('/etc/systemd/system')):
264 logging.warning("there is no /etc/systemd/system directory, cannot update default.target!")
265 return
266 default_target = self.path('/etc/systemd/system/default.target')
267 if os.path.islink(default_target):
268 os.unlink(default_target)
269 os.symlink('/lib/systemd/system/graphical.target', default_target)
271 class RPMMacroConfig(KickstartConfig):
272 """A class to apply the specified rpm macros to the filesystem"""
273 def apply(self, ks):
274 if not ks:
275 return
276 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
277 if exclude_docs(ks):
278 f.write("%_excludedocs 1\n")
279 if not selinux_enabled(ks):
280 f.write("%__file_context_path %{nil}\n")
281 if inst_langs(ks) != None:
282 f.write("%_install_langs ")
283 f.write(inst_langs(ks))
284 f.write("\n")
285 f.close()
287 class NetworkConfig(KickstartConfig):
288 """A class to apply a kickstart network configuration to a system."""
289 def write_ifcfg(self, network):
290 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
292 f = file(p, "w+")
293 os.chmod(p, 0644)
295 f.write("DEVICE=%s\n" % network.device)
296 f.write("BOOTPROTO=%s\n" % network.bootProto)
298 if network.bootProto.lower() == "static":
299 if network.ip:
300 f.write("IPADDR=%s\n" % network.ip)
301 if network.netmask:
302 f.write("NETMASK=%s\n" % network.netmask)
304 if network.onboot:
305 f.write("ONBOOT=on\n")
306 else:
307 f.write("ONBOOT=off\n")
309 if network.essid:
310 f.write("ESSID=%s\n" % network.essid)
312 if network.ethtool:
313 if network.ethtool.find("autoneg") == -1:
314 network.ethtool = "autoneg off " + network.ethtool
315 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
317 if network.bootProto.lower() == "dhcp":
318 if network.hostname:
319 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
320 if network.dhcpclass:
321 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
323 if network.mtu:
324 f.write("MTU=%s\n" % network.mtu)
326 f.close()
328 def write_wepkey(self, network):
329 if not network.wepkey:
330 return
332 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
333 f = file(p, "w+")
334 os.chmod(p, 0600)
335 f.write("KEY=%s\n" % network.wepkey)
336 f.close()
338 def write_sysconfig(self, useipv6, hostname, gateway):
339 path = self.path("/etc/sysconfig/network")
340 f = file(path, "w+")
341 os.chmod(path, 0644)
343 f.write("NETWORKING=yes\n")
345 if useipv6:
346 f.write("NETWORKING_IPV6=yes\n")
347 else:
348 f.write("NETWORKING_IPV6=no\n")
350 if gateway:
351 f.write("GATEWAY=%s\n" % gateway)
353 f.close()
355 def write_hosts(self, hostname):
356 localline = ""
357 if hostname and hostname != "localhost.localdomain":
358 localline += hostname + " "
359 l = hostname.split(".")
360 if len(l) > 1:
361 localline += l[0] + " "
362 localline += "localhost.localdomain localhost"
364 path = self.path("/etc/hosts")
365 f = file(path, "w+")
366 os.chmod(path, 0644)
367 f.write("127.0.0.1\t\t%s\n" % localline)
368 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
369 f.close()
371 def write_hostname(self, hostname):
372 if not hostname:
373 return
375 path = self.path("/etc/hostname")
376 f = file(path, "w+")
377 os.chmod(path, 0644)
378 f.write("%s\n" % (hostname,))
379 f.close()
381 def write_resolv(self, nodns, nameservers):
382 if nodns or not nameservers:
383 return
385 path = self.path("/etc/resolv.conf")
386 f = file(path, "w+")
387 os.chmod(path, 0644)
389 for ns in (nameservers):
390 if ns:
391 f.write("nameserver %s\n" % ns)
393 f.close()
395 def apply(self, ksnet):
396 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
398 useipv6 = False
399 nodns = False
400 hostname = None
401 gateway = None
402 nameservers = None
404 for network in ksnet.network:
405 if not network.device:
406 raise errors.KickstartError("No --device specified with "
407 "network kickstart command")
409 if (network.onboot and network.bootProto.lower() != "dhcp" and
410 not (network.ip and network.netmask)):
411 raise errors.KickstartError("No IP address and/or netmask "
412 "specified with static "
413 "configuration for '%s'" %
414 network.device)
416 self.write_ifcfg(network)
417 self.write_wepkey(network)
419 if network.ipv6:
420 useipv6 = True
421 if network.nodns:
422 nodns = True
424 if network.hostname:
425 hostname = network.hostname
426 if network.gateway:
427 gateway = network.gateway
429 if network.nameserver:
430 nameservers = network.nameserver.split(",")
432 self.write_sysconfig(useipv6, hostname, gateway)
433 self.write_hosts(hostname)
434 self.write_hostname(hostname)
435 self.write_resolv(nodns, nameservers)
437 class SelinuxConfig(KickstartConfig):
438 """A class to apply a kickstart selinux configuration to a system."""
439 def relabel(self, ksselinux):
440 # touch some files which get unhappy if they're not labeled correctly
441 for fn in ("/etc/resolv.conf",):
442 path = self.path(fn)
443 f = file(path, "w+")
444 os.chmod(path, 0644)
445 f.close()
447 if ksselinux.selinux == ksconstants.SELINUX_DISABLED:
448 return
450 if not os.path.exists(self.path("/sbin/setfiles")):
451 return
453 rc = self.call(["/sbin/setfiles", "-p", "-e", "/proc", "-e", "/sys", "-e", "/dev", selinux.selinux_file_context_path(), "/"])
454 if rc:
455 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
456 raise errors.KickstartError("SELinux relabel failed.")
457 else:
458 logging.error("SELinux relabel failed.")
460 def apply(self, ksselinux):
461 selinux_config = "/etc/selinux/config"
462 if not os.path.exists(self.instroot+selinux_config):
463 return
465 if ksselinux.selinux == ksconstants.SELINUX_ENFORCING:
466 cmd = "SELINUX=enforcing\n"
467 elif ksselinux.selinux == ksconstants.SELINUX_PERMISSIVE:
468 cmd = "SELINUX=permissive\n"
469 elif ksselinux.selinux == ksconstants.SELINUX_DISABLED:
470 cmd = "SELINUX=disabled\n"
471 else:
472 return
474 # Replace the SELINUX line in the config
475 lines = open(self.instroot+selinux_config).readlines()
476 with open(self.instroot+selinux_config, "w") as f:
477 for line in lines:
478 if line.startswith("SELINUX="):
479 f.write(cmd)
480 else:
481 f.write(line)
483 self.relabel(ksselinux)
485 def get_image_size(ks, default = None):
486 __size = 0
487 for p in ks.handler.partition.partitions:
488 if p.mountpoint == "/" and p.size:
489 __size = p.size
490 if __size > 0:
491 return int(__size) * 1024L * 1024L
492 else:
493 return default
495 def get_image_fstype(ks, default = None):
496 for p in ks.handler.partition.partitions:
497 if p.mountpoint == "/" and p.fstype:
498 return p.fstype
499 return default
501 def get_modules(ks):
502 devices = []
503 if not hasattr(ks.handler.device, "deviceList"):
504 devices.append(ks.handler.device)
505 else:
506 devices.extend(ks.handler.device.deviceList)
508 modules = []
509 for device in devices:
510 if not device.moduleName:
511 continue
512 modules.extend(device.moduleName.split(":"))
514 return modules
516 def get_timeout(ks, default = None):
517 if not hasattr(ks.handler.bootloader, "timeout"):
518 return default
519 if ks.handler.bootloader.timeout is None:
520 return default
521 return int(ks.handler.bootloader.timeout)
523 def get_kernel_args(ks, default = "ro rd.live.image quiet"):
524 if not hasattr(ks.handler.bootloader, "appendLine"):
525 return default
526 if ks.handler.bootloader.appendLine is None:
527 return default
528 return "%s %s" %(default, ks.handler.bootloader.appendLine)
530 def get_default_kernel(ks, default = None):
531 if not hasattr(ks.handler.bootloader, "default"):
532 return default
533 if not ks.handler.bootloader.default:
534 return default
535 return ks.handler.bootloader.default
537 def get_repos(ks, repo_urls = {}):
538 repos = {}
539 for repo in ks.handler.repo.repoList:
540 inc = []
541 if hasattr(repo, "includepkgs"):
542 inc.extend(repo.includepkgs)
544 exc = []
545 if hasattr(repo, "excludepkgs"):
546 exc.extend(repo.excludepkgs)
548 baseurl = repo.baseurl
549 mirrorlist = repo.mirrorlist
550 proxy = repo.proxy
551 sslverify = not repo.noverifyssl
553 if repo.name in repo_urls:
554 baseurl = repo_urls[repo.name]
555 mirrorlist = None
557 if repos.has_key(repo.name):
558 logging.warn("Overriding already specified repo %s" %(repo.name,))
559 repos[repo.name] = (repo.name, baseurl, mirrorlist, proxy, inc, exc, repo.cost, sslverify)
561 return repos.values()
563 def convert_method_to_repo(ks):
564 try:
565 ks.handler.repo.methodToRepo()
566 except (AttributeError, kserrors.KickstartError):
567 pass
569 def get_packages(ks, required = []):
570 return ks.handler.packages.packageList + required
572 def get_groups(ks, required = []):
573 return ks.handler.packages.groupList + required
575 def get_excluded(ks, required = []):
576 return ks.handler.packages.excludedList + required
578 def get_partitions(ks, required = []):
579 return ks.handler.partition.partitions
581 def ignore_missing(ks):
582 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
584 def exclude_docs(ks):
585 return ks.handler.packages.excludeDocs
587 def inst_langs(ks):
588 if hasattr(ks.handler.packages, "instLange"):
589 return ks.handler.packages.instLange
590 elif hasattr(ks.handler.packages, "instLangs"):
591 return ks.handler.packages.instLangs
592 return ""
594 def get_pre_scripts(ks):
595 scripts = []
596 for s in ks.handler.scripts:
597 if s.type != ksconstants.KS_SCRIPT_PRE:
598 continue
599 scripts.append(s)
600 return scripts
602 def get_post_scripts(ks):
603 scripts = []
604 for s in ks.handler.scripts:
605 if s.type != ksconstants.KS_SCRIPT_POST:
606 continue
607 scripts.append(s)
608 return scripts
610 def selinux_enabled(ks):
611 return ks.handler.selinux.selinux in (ksconstants.SELINUX_ENFORCING,
612 ksconstants.SELINUX_PERMISSIVE)