3 # Copyright (c) 2007 Red Hat, Inc.
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 from mic.utils import errors, misc, runner, fs_related as fs
28 import pykickstart.commands as kscommands
29 import pykickstart.constants as ksconstants
30 import pykickstart.errors as kserrors
31 import pykickstart.parser as ksparser
32 import pykickstart.version as ksversion
33 from pykickstart.handlers.control import commandMap
34 from pykickstart.handlers.control import dataMap
36 import custom_commands.desktop as desktop
37 import custom_commands.moblinrepo as moblinrepo
38 import custom_commands.micboot as micboot
40 def read_kickstart(path):
41 """Parse a kickstart file and return a KickstartParser instance.
43 This is a simple utility function which takes a path to a kickstart file,
44 parses it and returns a pykickstart KickstartParser instance which can
45 be then passed to an ImageCreator constructor.
47 If an error occurs, a CreatorError exception is thrown.
50 #version = ksversion.makeVersion()
51 #ks = ksparser.KickstartParser(version)
53 using_version = ksversion.DEVEL
54 commandMap[using_version]["desktop"] = desktop.Moblin_Desktop
55 commandMap[using_version]["repo"] = moblinrepo.Moblin_Repo
56 commandMap[using_version]["bootloader"] = micboot.Moblin_Bootloader
57 dataMap[using_version]["RepoData"] = moblinrepo.Moblin_RepoData
58 superclass = ksversion.returnClassForVersion(version=using_version)
60 class KSHandlers(superclass):
61 def __init__(self, mapping={}):
62 superclass.__init__(self, mapping=commandMap[using_version])
64 ks = ksparser.KickstartParser(KSHandlers())
67 ks.readKickstart(path)
68 except kserrors.KickstartError, e:
69 raise errors.KsError("'%s': %s" % (path, str(e)))
70 except kserrors.KickstartParseError, e:
71 raise errors.KsError("'%s': %s" % (path, str(e)))
74 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
75 """Construct and return an image name string.
77 This is a utility function to help create sensible name and fslabel
78 strings. The name is constructed using the sans-prefix-and-extension
79 kickstart filename and the supplied prefix and suffix.
81 If the name exceeds the maxlen length supplied, the prefix is first dropped
82 and then the kickstart filename portion is reduced until it fits. In other
83 words, the suffix takes precedence over the kickstart portion and the
84 kickstart portion takes precedence over the prefix.
86 kscfg -- a path to a kickstart file
87 prefix -- a prefix to prepend to the name; defaults to None, which causes
89 suffix -- a suffix to append to the name; defaults to None, which causes
90 a YYYYMMDDHHMM suffix to be used
91 maxlen -- the maximum length for the returned string; defaults to None,
92 which means there is no restriction on the name length
94 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
97 name = os.path.basename(kscfg)
105 suffix = time.strftime("%Y%m%d%H%M")
107 if name.startswith(prefix):
108 name = name[len(prefix):]
110 ret = prefix + name + "-" + suffix
111 if not maxlen is None and len(ret) > maxlen:
112 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
116 class KickstartConfig(object):
117 """A base class for applying kickstart configurations to a system."""
118 def __init__(self, instroot):
119 self.instroot = instroot
121 def path(self, subpath):
122 return self.instroot + subpath
125 os.chroot(self.instroot)
128 def call(self, args):
129 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
130 msger.warning("%s/%s" %(self.instroot, args[0]))
131 raise errors.KsError("Unable to run %s!" %(args))
132 subprocess.call(args, preexec_fn = self.chroot)
137 class LanguageConfig(KickstartConfig):
138 """A class to apply a kickstart language configuration to a system."""
139 def apply(self, kslang):
140 if not os.path.exists(self.path("/etc/sysconfig")):
141 os.mkdir(self.path("/etc/sysconfig"))
143 f = open(self.path("/etc/sysconfig/i18n"), "w+")
144 f.write("LANG=\"" + kslang.lang + "\"\n")
147 class KeyboardConfig(KickstartConfig):
148 """A class to apply a kickstart keyboard configuration to a system."""
149 def apply(self, kskeyboard):
152 # should this impact the X keyboard config too?
153 # or do we want to make X be able to do this mapping?
155 #k = rhpl.keyboard.Keyboard()
156 #if kskeyboard.keyboard:
157 # k.set(kskeyboard.keyboard)
158 #k.write(self.instroot)
161 class TimezoneConfig(KickstartConfig):
162 """A class to apply a kickstart timezone configuration to a system."""
163 def apply(self, kstimezone):
164 if not os.path.exists(self.path("/etc/sysconfig")):
165 os.mkdir(self.path("/etc/sysconfig"))
166 tz = kstimezone.timezone or "America/New_York"
167 utc = str(kstimezone.isUtc)
169 f = open(self.path("/etc/sysconfig/clock"), "w+")
170 f.write("ZONE=\"" + tz + "\"\n")
171 f.write("UTC=" + utc + "\n")
174 shutil.copyfile(self.path("/usr/share/zoneinfo/%s" %(tz,)),
175 self.path("/etc/localtime"))
176 except (IOError, OSError), (errno, msg):
177 raise errors.KsError("Error copying timezone info: %s" %(msg,))
180 class AuthConfig(KickstartConfig):
181 """A class to apply a kickstart authconfig configuration to a system."""
182 def apply(self, ksauthconfig):
183 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
184 args = ["/usr/share/authconfig/authconfig.py", "--update", "--nostart"]
185 self.call(args + auth.split())
187 class FirewallConfig(KickstartConfig):
188 """A class to apply a kickstart firewall configuration to a system."""
189 def apply(self, ksfirewall):
191 # FIXME: should handle the rest of the options
193 if not os.path.exists(self.path("/usr/sbin/lokkit")):
195 if ksfirewall.enabled:
198 status = "--disabled"
200 self.call(["/usr/sbin/lokkit",
201 "-f", "--quiet", "--nostart", status])
203 class RootPasswordConfig(KickstartConfig):
204 """A class to apply a kickstart root password configuration to a system."""
206 self.call(["/usr/bin/passwd", "-d", "root"])
208 def set_encrypted(self, password):
209 self.call(["/usr/sbin/usermod", "-p", password, "root"])
211 def set_unencrypted(self, password):
212 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
213 if not os.path.exists("%s/%s" %(self.instroot, p)):
214 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
216 p1 = subprocess.Popen(["/bin/echo", "root:%s" %password],
217 stdout = subprocess.PIPE,
218 preexec_fn = self.chroot)
219 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
221 stdout = subprocess.PIPE,
222 preexec_fn = self.chroot)
225 def apply(self, ksrootpw):
226 if ksrootpw.isCrypted:
227 self.set_encrypted(ksrootpw.password)
228 elif ksrootpw.password != "":
229 self.set_unencrypted(ksrootpw.password)
233 class UserConfig(KickstartConfig):
234 def set_empty_passwd(self, user):
235 self.call(["/usr/bin/passwd", "-d", user])
237 def set_encrypted_passwd(self, user, password):
238 self.call(["/usr/sbin/usermod", "-p", "%s" % password, user])
240 def set_unencrypted_passwd(self, user, password):
241 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
242 if not os.path.exists("%s/%s" %(self.instroot, p)):
243 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
245 p1 = subprocess.Popen(["/bin/echo", "%s:%s" %(user, password)],
246 stdout = subprocess.PIPE,
247 preexec_fn = self.chroot)
248 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
250 stdout = subprocess.PIPE,
251 preexec_fn = self.chroot)
254 def addUser(self, userconfig):
255 args = [ "/usr/sbin/useradd" ]
256 if userconfig.groups:
257 args += [ "--groups", string.join(userconfig.groups, ",") ]
259 args.append(userconfig.name)
260 dev_null = os.open("/dev/null", os.O_WRONLY)
261 subprocess.call(args,
264 preexec_fn = self.chroot)
266 if userconfig.password not in (None, ""):
267 if userconfig.isCrypted:
268 self.set_encrypted_passwd(userconfig.name, userconfig.password)
270 self.set_unencrypted_passwd(userconfig.name, userconfig.password)
272 self.set_empty_passwd(userconfig.name)
274 raise errors.KsError("Invalid kickstart command: %s" % userconfig.__str__())
276 def apply(self, user):
277 for userconfig in user.userList:
279 self.addUser(userconfig)
283 class ServicesConfig(KickstartConfig):
284 """A class to apply a kickstart services configuration to a system."""
285 def apply(self, ksservices):
286 if not os.path.exists(self.path("/sbin/chkconfig")):
288 for s in ksservices.enabled:
289 self.call(["/sbin/chkconfig", s, "on"])
290 for s in ksservices.disabled:
291 self.call(["/sbin/chkconfig", s, "off"])
293 class XConfig(KickstartConfig):
294 """A class to apply a kickstart X configuration to a system."""
295 def apply(self, ksxconfig):
297 f = open(self.path("/etc/inittab"), "rw+")
299 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
303 if ksxconfig.defaultdesktop:
304 f = open(self.path("/etc/sysconfig/desktop"), "w")
305 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
308 class DesktopConfig(KickstartConfig):
309 """A class to apply a kickstart desktop configuration to a system."""
310 def apply(self, ksdesktop):
311 if ksdesktop.defaultdesktop:
312 f = open(self.path("/etc/sysconfig/desktop"), "w")
313 f.write("DESKTOP="+ksdesktop.defaultdesktop+"\n")
315 if os.path.exists(self.path("/etc/gdm/custom.conf")):
316 f = open(self.path("/etc/skel/.dmrc"), "w")
317 f.write("[Desktop]\n")
318 f.write("Session="+ksdesktop.defaultdesktop.lower()+"\n")
320 if ksdesktop.session:
321 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
322 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
323 f.write("session="+ksdesktop.session.lower()+"\n")
325 if ksdesktop.autologinuser:
326 f = open(self.path("/etc/sysconfig/desktop"), "a+")
327 f.write("AUTOLOGIN_USER=" + ksdesktop.autologinuser + "\n")
329 if ksdesktop.session:
330 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
331 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
332 f.write("user="+ksdesktop.autologinuser+"\n")
334 if os.path.exists(self.path("/etc/gdm/custom.conf")):
335 f = open(self.path("/etc/gdm/custom.conf"), "w")
336 f.write("[daemon]\n")
337 f.write("AutomaticLoginEnable=true\n")
338 f.write("AutomaticLogin=" + ksdesktop.autologinuser + "\n")
341 class MoblinRepoConfig(KickstartConfig):
342 """A class to apply a kickstart desktop configuration to a system."""
343 def __create_repo_section(self, repo, type, fd):
346 reposuffix = {"base":"", "debuginfo":"-debuginfo", "source":"-source"}
347 reponame = repo.name + reposuffix[type]
350 baseurl = repo.baseurl
352 mirrorlist = repo.mirrorlist
353 elif type == "debuginfo":
355 if repo.baseurl.endswith("/"):
356 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
358 baseurl = os.path.dirname(repo.baseurl)
361 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
362 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
363 mirrorlist += "debug" + "-" + variant
364 elif type == "source":
366 if repo.baseurl.endswith("/"):
367 baseurl = os.path.dirname(os.path.dirname(os.path.dirname(repo.baseurl)))
369 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
372 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
373 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
374 mirrorlist += "source" + "-" + variant
376 fd.write("[" + reponame + "]\n")
377 fd.write("name=" + reponame + "\n")
378 fd.write("failovermethod=priority\n")
380 fd.write("baseurl=" + baseurl + "\n")
382 fd.write("mirrorlist=" + mirrorlist + "\n")
383 """ Skip saving proxy settings """
385 # fd.write("proxy=" + repo.proxy + "\n")
386 #if repo.proxy_username:
387 # fd.write("proxy_username=" + repo.proxy_username + "\n")
388 #if repo.proxy_password:
389 # fd.write("proxy_password=" + repo.proxy_password + "\n")
391 fd.write("gpgkey=" + repo.gpgkey + "\n")
392 fd.write("gpgcheck=1\n")
394 fd.write("gpgcheck=0\n")
395 if type == "source" or type == "debuginfo" or repo.disable:
396 fd.write("enabled=0\n")
398 fd.write("enabled=1\n")
401 def __create_repo_file(self, repo, repodir):
402 fs.makedirs(self.path(repodir))
403 f = open(self.path(repodir + "/" + repo.name + ".repo"), "w")
404 self.__create_repo_section(repo, "base", f)
406 self.__create_repo_section(repo, "debuginfo", f)
408 self.__create_repo_section(repo, "source", f)
411 def apply(self, ksrepo, repodata):
412 for repo in ksrepo.repoList:
414 #self.__create_repo_file(repo, "/etc/yum.repos.d")
415 self.__create_repo_file(repo, "/etc/zypp/repos.d")
416 """ Import repo gpg keys """
418 for repo in repodata:
420 runner.quiet(['rpm', "--root=%s" % self.instroot, "--import", repo['repokey']])
422 class RPMMacroConfig(KickstartConfig):
423 """A class to apply the specified rpm macros to the filesystem"""
427 if not os.path.exists(self.path("/etc/rpm")):
428 os.mkdir(self.path("/etc/rpm"))
429 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
431 f.write("%_excludedocs 1\n")
432 f.write("%__file_context_path %{nil}\n")
433 if inst_langs(ks) != None:
434 f.write("%_install_langs ")
435 f.write(inst_langs(ks))
439 class NetworkConfig(KickstartConfig):
440 """A class to apply a kickstart network configuration to a system."""
441 def write_ifcfg(self, network):
442 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
447 f.write("DEVICE=%s\n" % network.device)
448 f.write("BOOTPROTO=%s\n" % network.bootProto)
450 if network.bootProto.lower() == "static":
452 f.write("IPADDR=%s\n" % network.ip)
454 f.write("NETMASK=%s\n" % network.netmask)
457 f.write("ONBOOT=on\n")
459 f.write("ONBOOT=off\n")
462 f.write("ESSID=%s\n" % network.essid)
465 if network.ethtool.find("autoneg") == -1:
466 network.ethtool = "autoneg off " + network.ethtool
467 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
469 if network.bootProto.lower() == "dhcp":
471 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
472 if network.dhcpclass:
473 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
476 f.write("MTU=%s\n" % network.mtu)
480 def write_wepkey(self, network):
481 if not network.wepkey:
484 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
487 f.write("KEY=%s\n" % network.wepkey)
490 def write_sysconfig(self, useipv6, hostname, gateway):
491 path = self.path("/etc/sysconfig/network")
495 f.write("NETWORKING=yes\n")
498 f.write("NETWORKING_IPV6=yes\n")
500 f.write("NETWORKING_IPV6=no\n")
503 f.write("HOSTNAME=%s\n" % hostname)
505 f.write("HOSTNAME=localhost.localdomain\n")
508 f.write("GATEWAY=%s\n" % gateway)
512 def write_hosts(self, hostname):
514 if hostname and hostname != "localhost.localdomain":
515 localline += hostname + " "
516 l = hostname.split(".")
518 localline += l[0] + " "
519 localline += "localhost.localdomain localhost"
521 path = self.path("/etc/hosts")
524 f.write("127.0.0.1\t\t%s\n" % localline)
525 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
528 def write_resolv(self, nodns, nameservers):
529 if nodns or not nameservers:
532 path = self.path("/etc/resolv.conf")
536 for ns in (nameservers):
538 f.write("nameserver %s\n" % ns)
542 def apply(self, ksnet):
543 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
551 for network in ksnet.network:
552 if not network.device:
553 raise errors.KsError("No --device specified with "
554 "network kickstart command")
556 if (network.onboot and network.bootProto.lower() != "dhcp" and
557 not (network.ip and network.netmask)):
558 raise errors.KsError("No IP address and/or netmask "
559 "specified with static "
560 "configuration for '%s'" %
563 self.write_ifcfg(network)
564 self.write_wepkey(network)
572 hostname = network.hostname
574 gateway = network.gateway
576 if network.nameserver:
577 nameservers = network.nameserver.split(",")
579 self.write_sysconfig(useipv6, hostname, gateway)
580 self.write_hosts(hostname)
581 self.write_resolv(nodns, nameservers)
584 def get_image_size(ks, default = None):
586 for p in ks.handler.partition.partitions:
587 if p.mountpoint == "/" and p.size:
590 return int(__size) * 1024L * 1024L
594 def get_image_fstype(ks, default = None):
595 for p in ks.handler.partition.partitions:
596 if p.mountpoint == "/" and p.fstype:
600 def get_image_fsopts(ks, default = None):
601 for p in ks.handler.partition.partitions:
602 if p.mountpoint == "/" and p.fsopts:
608 if isinstance(ks.handler.device, kscommands.device.FC3_Device):
609 devices.append(ks.handler.device)
611 devices.extend(ks.handler.device.deviceList)
614 for device in devices:
615 if not device.moduleName:
617 modules.extend(device.moduleName.split(":"))
621 def get_timeout(ks, default = None):
622 if not hasattr(ks.handler.bootloader, "timeout"):
624 if ks.handler.bootloader.timeout is None:
626 return int(ks.handler.bootloader.timeout)
628 def get_kernel_args(ks, default = "ro liveimg"):
629 if not hasattr(ks.handler.bootloader, "appendLine"):
631 if ks.handler.bootloader.appendLine is None:
633 return "%s %s" %(default, ks.handler.bootloader.appendLine)
635 def get_menu_args(ks, default = "liveinst"):
636 if not hasattr(ks.handler.bootloader, "menus"):
638 if ks.handler.bootloader.menus in (None, ""):
640 return "%s" % ks.handler.bootloader.menus
642 def get_default_kernel(ks, default = None):
643 if not hasattr(ks.handler.bootloader, "default"):
645 if not ks.handler.bootloader.default:
647 return ks.handler.bootloader.default
649 def get_repos(ks, repo_urls = {}):
651 for repo in ks.handler.repo.repoList:
653 if hasattr(repo, "includepkgs"):
654 inc.extend(repo.includepkgs)
657 if hasattr(repo, "excludepkgs"):
658 exc.extend(repo.excludepkgs)
660 baseurl = repo.baseurl
661 mirrorlist = repo.mirrorlist
663 if repo.name in repo_urls:
664 baseurl = repo_urls[repo.name]
667 if repos.has_key(repo.name):
668 msger.warning("Overriding already specified repo %s" %(repo.name,))
671 if hasattr(repo, "proxy"):
673 proxy_username = None
674 if hasattr(repo, "proxy_username"):
675 proxy_username = repo.proxy_username
676 proxy_password = None
677 if hasattr(repo, "proxy_password"):
678 proxy_password = repo.proxy_password
679 if hasattr(repo, "debuginfo"):
680 debuginfo = repo.debuginfo
681 if hasattr(repo, "source"):
683 if hasattr(repo, "gpgkey"):
685 if hasattr(repo, "disable"):
686 disable = repo.disable
688 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable)
690 return repos.values()
692 def convert_method_to_repo(ks):
694 ks.handler.repo.methodToRepo()
695 except (AttributeError, kserrors.KickstartError):
698 def get_packages(ks, required = []):
699 return ks.handler.packages.packageList + required
701 def get_groups(ks, required = []):
702 return ks.handler.packages.groupList + required
704 def get_excluded(ks, required = []):
705 return ks.handler.packages.excludedList + required
707 def get_partitions(ks, required = []):
708 return ks.handler.partition.partitions
710 def ignore_missing(ks):
711 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
713 def exclude_docs(ks):
714 return ks.handler.packages.excludeDocs
717 if hasattr(ks.handler.packages, "instLange"):
718 return ks.handler.packages.instLange
719 elif hasattr(ks.handler.packages, "instLangs"):
720 return ks.handler.packages.instLangs
723 def get_post_scripts(ks):
725 for s in ks.handler.scripts:
726 if s.type != ksparser.KS_SCRIPT_POST:
731 def add_repo(ks, repostr):
732 args = repostr.split()
733 repoobj = ks.handler.repo.parse(args[1:])
734 if repoobj and repoobj not in ks.handler.repo.repoList:
735 ks.handler.repo.repoList.append(repoobj)
737 def remove_all_repos(ks):
738 while len(ks.handler.repo.repoList) != 0:
739 del ks.handler.repo.repoList[0]
741 def remove_duplicate_repos(ks):
745 if len(ks.handler.repo.repoList) < 2:
747 if i >= len(ks.handler.repo.repoList) - 1:
749 name = ks.handler.repo.repoList[i].name
750 baseurl = ks.handler.repo.repoList[i].baseurl
751 if j < len(ks.handler.repo.repoList):
752 if (ks.handler.repo.repoList[j].name == name or \
753 ks.handler.repo.repoList[j].baseurl == baseurl):
754 del ks.handler.repo.repoList[j]
757 if j >= len(ks.handler.repo.repoList):
764 def resolve_groups(creatoropts, repometadata):
766 if 'zypp' == creatoropts['pkgmgr']:
768 ks = creatoropts['ks']
770 for repo in repometadata:
771 """ Mustn't replace group with package list if repo is ready for the corresponding package manager """
772 if iszypp and repo["patterns"]:
774 if not iszypp and repo["comps"]:
778 But we also must handle such cases, use zypp but repo only has comps,
779 use yum but repo only has patterns, use zypp but use_comps is true,
780 use yum but use_comps is false.
783 if iszypp and repo["comps"]:
784 groupfile = repo["comps"]
785 get_pkglist_handler = misc.get_pkglist_in_comps
786 if not iszypp and repo["patterns"]:
787 groupfile = repo["patterns"]
788 get_pkglist_handler = misc.get_pkglist_in_patterns
793 if i >= len(ks.handler.packages.groupList):
795 pkglist = get_pkglist_handler(ks.handler.packages.groupList[i].name, groupfile)
797 del ks.handler.packages.groupList[i]
799 if pkg not in ks.handler.packages.packageList:
800 ks.handler.packages.packageList.append(pkg)