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
124 def _check_sysconfig(self):
125 if not os.path.exists(self.path("/etc/sysconfig")):
126 fs.makedirs(self.path("/etc/sysconfig"))
129 os.chroot(self.instroot)
132 def call(self, args):
133 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
134 msger.warning("%s/%s" %(self.instroot, args[0]))
135 raise errors.KsError("Unable to run %s!" %(args))
136 subprocess.call(args, preexec_fn = self.chroot)
141 class LanguageConfig(KickstartConfig):
142 """A class to apply a kickstart language configuration to a system."""
143 def apply(self, kslang):
144 self._check_sysconfig()
146 f = open(self.path("/etc/sysconfig/i18n"), "w+")
147 f.write("LANG=\"" + kslang.lang + "\"\n")
150 class KeyboardConfig(KickstartConfig):
151 """A class to apply a kickstart keyboard configuration to a system."""
152 def apply(self, kskeyboard):
155 # should this impact the X keyboard config too?
156 # or do we want to make X be able to do this mapping?
158 #k = rhpl.keyboard.Keyboard()
159 #if kskeyboard.keyboard:
160 # k.set(kskeyboard.keyboard)
161 #k.write(self.instroot)
164 class TimezoneConfig(KickstartConfig):
165 """A class to apply a kickstart timezone configuration to a system."""
166 def apply(self, kstimezone):
167 self._check_sysconfig()
168 tz = kstimezone.timezone or "America/New_York"
169 utc = str(kstimezone.isUtc)
171 f = open(self.path("/etc/sysconfig/clock"), "w+")
172 f.write("ZONE=\"" + tz + "\"\n")
173 f.write("UTC=" + utc + "\n")
176 shutil.copyfile(self.path("/usr/share/zoneinfo/%s" %(tz,)),
177 self.path("/etc/localtime"))
178 except (IOError, OSError), (errno, msg):
179 raise errors.KsError("Error copying timezone info: %s" %(msg,))
182 class AuthConfig(KickstartConfig):
183 """A class to apply a kickstart authconfig configuration to a system."""
184 def apply(self, ksauthconfig):
185 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
186 args = ["/usr/share/authconfig/authconfig.py", "--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):
193 # FIXME: should handle the rest of the options
195 if not os.path.exists(self.path("/usr/sbin/lokkit")):
197 if ksfirewall.enabled:
200 status = "--disabled"
202 self.call(["/usr/sbin/lokkit",
203 "-f", "--quiet", "--nostart", status])
205 class RootPasswordConfig(KickstartConfig):
206 """A class to apply a kickstart root password configuration to a system."""
208 self.call(["/usr/bin/passwd", "-d", "root"])
210 def set_encrypted(self, password):
211 self.call(["/usr/sbin/usermod", "-p", password, "root"])
213 def set_unencrypted(self, password):
214 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
215 if not os.path.exists("%s/%s" %(self.instroot, p)):
216 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
218 p1 = subprocess.Popen(["/bin/echo", "root:%s" %password],
219 stdout = subprocess.PIPE,
220 preexec_fn = self.chroot)
221 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
223 stdout = subprocess.PIPE,
224 preexec_fn = self.chroot)
227 def apply(self, ksrootpw):
228 if ksrootpw.isCrypted:
229 self.set_encrypted(ksrootpw.password)
230 elif ksrootpw.password != "":
231 self.set_unencrypted(ksrootpw.password)
235 class UserConfig(KickstartConfig):
236 def set_empty_passwd(self, user):
237 self.call(["/usr/bin/passwd", "-d", user])
239 def set_encrypted_passwd(self, user, password):
240 self.call(["/usr/sbin/usermod", "-p", "%s" % password, user])
242 def set_unencrypted_passwd(self, user, password):
243 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
244 if not os.path.exists("%s/%s" %(self.instroot, p)):
245 raise errors.KsError("Unable to set unencrypted password due to lack of %s" % p)
247 p1 = subprocess.Popen(["/bin/echo", "%s:%s" %(user, password)],
248 stdout = subprocess.PIPE,
249 preexec_fn = self.chroot)
250 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
252 stdout = subprocess.PIPE,
253 preexec_fn = self.chroot)
256 def addUser(self, userconfig):
257 args = [ "/usr/sbin/useradd" ]
258 if userconfig.groups:
259 args += [ "--groups", string.join(userconfig.groups, ",") ]
261 args.append(userconfig.name)
262 dev_null = os.open("/dev/null", os.O_WRONLY)
263 subprocess.call(args,
266 preexec_fn = self.chroot)
268 if userconfig.password not in (None, ""):
269 if userconfig.isCrypted:
270 self.set_encrypted_passwd(userconfig.name, userconfig.password)
272 self.set_unencrypted_passwd(userconfig.name, userconfig.password)
274 self.set_empty_passwd(userconfig.name)
276 raise errors.KsError("Invalid kickstart command: %s" % userconfig.__str__())
278 def apply(self, user):
279 for userconfig in user.userList:
281 self.addUser(userconfig)
285 class ServicesConfig(KickstartConfig):
286 """A class to apply a kickstart services configuration to a system."""
287 def apply(self, ksservices):
288 if not os.path.exists(self.path("/sbin/chkconfig")):
290 for s in ksservices.enabled:
291 self.call(["/sbin/chkconfig", s, "on"])
292 for s in ksservices.disabled:
293 self.call(["/sbin/chkconfig", s, "off"])
295 class XConfig(KickstartConfig):
296 """A class to apply a kickstart X configuration to a system."""
297 def apply(self, ksxconfig):
298 if ksxconfig.startX and os.path.exists(self.path("/etc/inittab")):
299 f = open(self.path("/etc/inittab"), "rw+")
301 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
305 if ksxconfig.defaultdesktop:
306 self._check_sysconfig()
307 f = open(self.path("/etc/sysconfig/desktop"), "w")
308 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
311 class DesktopConfig(KickstartConfig):
312 """A class to apply a kickstart desktop configuration to a system."""
313 def apply(self, ksdesktop):
314 if ksdesktop.defaultdesktop:
315 self._check_sysconfig()
316 f = open(self.path("/etc/sysconfig/desktop"), "w")
317 f.write("DESKTOP="+ksdesktop.defaultdesktop+"\n")
319 if os.path.exists(self.path("/etc/gdm/custom.conf")):
320 f = open(self.path("/etc/skel/.dmrc"), "w")
321 f.write("[Desktop]\n")
322 f.write("Session="+ksdesktop.defaultdesktop.lower()+"\n")
324 if ksdesktop.session:
325 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
326 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
327 f.write("session="+ksdesktop.session.lower()+"\n")
329 if ksdesktop.autologinuser:
330 self._check_sysconfig()
331 f = open(self.path("/etc/sysconfig/desktop"), "a+")
332 f.write("AUTOLOGIN_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)