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 os.path.exists(self.path("/etc/gdm/custom.conf")):
330 f = open(self.path("/etc/gdm/custom.conf"), "w")
331 f.write("[daemon]\n")
332 f.write("AutomaticLoginEnable=true\n")
333 f.write("AutomaticLogin=" + ksdesktop.autologinuser + "\n")
336 class MoblinRepoConfig(KickstartConfig):
337 """A class to apply a kickstart desktop configuration to a system."""
338 def __create_repo_section(self, repo, type, fd):
341 reposuffix = {"base":"", "debuginfo":"-debuginfo", "source":"-source"}
342 reponame = repo.name + reposuffix[type]
345 baseurl = repo.baseurl
347 mirrorlist = repo.mirrorlist
348 elif type == "debuginfo":
350 if repo.baseurl.endswith("/"):
351 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
353 baseurl = os.path.dirname(repo.baseurl)
356 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
357 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
358 mirrorlist += "debug" + "-" + variant
359 elif type == "source":
361 if repo.baseurl.endswith("/"):
362 baseurl = os.path.dirname(os.path.dirname(os.path.dirname(repo.baseurl)))
364 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
367 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
368 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
369 mirrorlist += "source" + "-" + variant
371 fd.write("[" + reponame + "]\n")
372 fd.write("name=" + reponame + "\n")
373 fd.write("failovermethod=priority\n")
375 fd.write("baseurl=" + baseurl + "\n")
377 fd.write("mirrorlist=" + mirrorlist + "\n")
378 """ Skip saving proxy settings """
380 # fd.write("proxy=" + repo.proxy + "\n")
381 #if repo.proxy_username:
382 # fd.write("proxy_username=" + repo.proxy_username + "\n")
383 #if repo.proxy_password:
384 # fd.write("proxy_password=" + repo.proxy_password + "\n")
386 fd.write("gpgkey=" + repo.gpgkey + "\n")
387 fd.write("gpgcheck=1\n")
389 fd.write("gpgcheck=0\n")
390 if type == "source" or type == "debuginfo" or repo.disable:
391 fd.write("enabled=0\n")
393 fd.write("enabled=1\n")
396 def __create_repo_file(self, repo, repodir):
397 fs.makedirs(self.path(repodir))
398 f = open(self.path(repodir + "/" + repo.name + ".repo"), "w")
399 self.__create_repo_section(repo, "base", f)
401 self.__create_repo_section(repo, "debuginfo", f)
403 self.__create_repo_section(repo, "source", f)
406 def apply(self, ksrepo, repodata):
407 for repo in ksrepo.repoList:
409 #self.__create_repo_file(repo, "/etc/yum.repos.d")
410 self.__create_repo_file(repo, "/etc/zypp/repos.d")
411 """ Import repo gpg keys """
413 for repo in repodata:
415 runner.quiet(['rpm', "--root=%s" % self.instroot, "--import", repo['repokey']])
417 class RPMMacroConfig(KickstartConfig):
418 """A class to apply the specified rpm macros to the filesystem"""
422 if not os.path.exists(self.path("/etc/rpm")):
423 os.mkdir(self.path("/etc/rpm"))
424 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
426 f.write("%_excludedocs 1\n")
427 f.write("%__file_context_path %{nil}\n")
428 if inst_langs(ks) != None:
429 f.write("%_install_langs ")
430 f.write(inst_langs(ks))
434 class NetworkConfig(KickstartConfig):
435 """A class to apply a kickstart network configuration to a system."""
436 def write_ifcfg(self, network):
437 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
442 f.write("DEVICE=%s\n" % network.device)
443 f.write("BOOTPROTO=%s\n" % network.bootProto)
445 if network.bootProto.lower() == "static":
447 f.write("IPADDR=%s\n" % network.ip)
449 f.write("NETMASK=%s\n" % network.netmask)
452 f.write("ONBOOT=on\n")
454 f.write("ONBOOT=off\n")
457 f.write("ESSID=%s\n" % network.essid)
460 if network.ethtool.find("autoneg") == -1:
461 network.ethtool = "autoneg off " + network.ethtool
462 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
464 if network.bootProto.lower() == "dhcp":
466 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
467 if network.dhcpclass:
468 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
471 f.write("MTU=%s\n" % network.mtu)
475 def write_wepkey(self, network):
476 if not network.wepkey:
479 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
482 f.write("KEY=%s\n" % network.wepkey)
485 def write_sysconfig(self, useipv6, hostname, gateway):
486 path = self.path("/etc/sysconfig/network")
490 f.write("NETWORKING=yes\n")
493 f.write("NETWORKING_IPV6=yes\n")
495 f.write("NETWORKING_IPV6=no\n")
498 f.write("HOSTNAME=%s\n" % hostname)
500 f.write("HOSTNAME=localhost.localdomain\n")
503 f.write("GATEWAY=%s\n" % gateway)
507 def write_hosts(self, hostname):
509 if hostname and hostname != "localhost.localdomain":
510 localline += hostname + " "
511 l = hostname.split(".")
513 localline += l[0] + " "
514 localline += "localhost.localdomain localhost"
516 path = self.path("/etc/hosts")
519 f.write("127.0.0.1\t\t%s\n" % localline)
520 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
523 def write_resolv(self, nodns, nameservers):
524 if nodns or not nameservers:
527 path = self.path("/etc/resolv.conf")
531 for ns in (nameservers):
533 f.write("nameserver %s\n" % ns)
537 def apply(self, ksnet):
538 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
546 for network in ksnet.network:
547 if not network.device:
548 raise errors.KsError("No --device specified with "
549 "network kickstart command")
551 if (network.onboot and network.bootProto.lower() != "dhcp" and
552 not (network.ip and network.netmask)):
553 raise errors.KsError("No IP address and/or netmask "
554 "specified with static "
555 "configuration for '%s'" %
558 self.write_ifcfg(network)
559 self.write_wepkey(network)
567 hostname = network.hostname
569 gateway = network.gateway
571 if network.nameserver:
572 nameservers = network.nameserver.split(",")
574 self.write_sysconfig(useipv6, hostname, gateway)
575 self.write_hosts(hostname)
576 self.write_resolv(nodns, nameservers)
579 def get_image_size(ks, default = None):
581 for p in ks.handler.partition.partitions:
582 if p.mountpoint == "/" and p.size:
585 return int(__size) * 1024L * 1024L
589 def get_image_fstype(ks, default = None):
590 for p in ks.handler.partition.partitions:
591 if p.mountpoint == "/" and p.fstype:
595 def get_image_fsopts(ks, default = None):
596 for p in ks.handler.partition.partitions:
597 if p.mountpoint == "/" and p.fsopts:
603 if isinstance(ks.handler.device, kscommands.device.FC3_Device):
604 devices.append(ks.handler.device)
606 devices.extend(ks.handler.device.deviceList)
609 for device in devices:
610 if not device.moduleName:
612 modules.extend(device.moduleName.split(":"))
616 def get_timeout(ks, default = None):
617 if not hasattr(ks.handler.bootloader, "timeout"):
619 if ks.handler.bootloader.timeout is None:
621 return int(ks.handler.bootloader.timeout)
623 def get_kernel_args(ks, default = "ro liveimg"):
624 if not hasattr(ks.handler.bootloader, "appendLine"):
626 if ks.handler.bootloader.appendLine is None:
628 return "%s %s" %(default, ks.handler.bootloader.appendLine)
630 def get_menu_args(ks, default = "liveinst"):
631 if not hasattr(ks.handler.bootloader, "menus"):
633 if ks.handler.bootloader.menus in (None, ""):
635 return "%s" % ks.handler.bootloader.menus
637 def get_default_kernel(ks, default = None):
638 if not hasattr(ks.handler.bootloader, "default"):
640 if not ks.handler.bootloader.default:
642 return ks.handler.bootloader.default
644 def get_repos(ks, repo_urls = {}):
646 for repo in ks.handler.repo.repoList:
648 if hasattr(repo, "includepkgs"):
649 inc.extend(repo.includepkgs)
652 if hasattr(repo, "excludepkgs"):
653 exc.extend(repo.excludepkgs)
655 baseurl = repo.baseurl
656 mirrorlist = repo.mirrorlist
658 if repo.name in repo_urls:
659 baseurl = repo_urls[repo.name]
662 if repos.has_key(repo.name):
663 msger.warning("Overriding already specified repo %s" %(repo.name,))
666 if hasattr(repo, "proxy"):
668 proxy_username = None
669 if hasattr(repo, "proxy_username"):
670 proxy_username = repo.proxy_username
671 proxy_password = None
672 if hasattr(repo, "proxy_password"):
673 proxy_password = repo.proxy_password
674 if hasattr(repo, "debuginfo"):
675 debuginfo = repo.debuginfo
676 if hasattr(repo, "source"):
678 if hasattr(repo, "gpgkey"):
680 if hasattr(repo, "disable"):
681 disable = repo.disable
683 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable)
685 return repos.values()
687 def convert_method_to_repo(ks):
689 ks.handler.repo.methodToRepo()
690 except (AttributeError, kserrors.KickstartError):
693 def get_packages(ks, required = []):
694 return ks.handler.packages.packageList + required
696 def get_groups(ks, required = []):
697 return ks.handler.packages.groupList + required
699 def get_excluded(ks, required = []):
700 return ks.handler.packages.excludedList + required
702 def get_partitions(ks, required = []):
703 return ks.handler.partition.partitions
705 def ignore_missing(ks):
706 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
708 def exclude_docs(ks):
709 return ks.handler.packages.excludeDocs
712 if hasattr(ks.handler.packages, "instLange"):
713 return ks.handler.packages.instLange
714 elif hasattr(ks.handler.packages, "instLangs"):
715 return ks.handler.packages.instLangs
718 def get_post_scripts(ks):
720 for s in ks.handler.scripts:
721 if s.type != ksparser.KS_SCRIPT_POST:
726 def add_repo(ks, repostr):
727 args = repostr.split()
728 repoobj = ks.handler.repo.parse(args[1:])
729 if repoobj and repoobj not in ks.handler.repo.repoList:
730 ks.handler.repo.repoList.append(repoobj)
732 def remove_all_repos(ks):
733 while len(ks.handler.repo.repoList) != 0:
734 del ks.handler.repo.repoList[0]
736 def remove_duplicate_repos(ks):
740 if len(ks.handler.repo.repoList) < 2:
742 if i >= len(ks.handler.repo.repoList) - 1:
744 name = ks.handler.repo.repoList[i].name
745 baseurl = ks.handler.repo.repoList[i].baseurl
746 if j < len(ks.handler.repo.repoList):
747 if (ks.handler.repo.repoList[j].name == name or \
748 ks.handler.repo.repoList[j].baseurl == baseurl):
749 del ks.handler.repo.repoList[j]
752 if j >= len(ks.handler.repo.repoList):
759 def resolve_groups(creatoropts, repometadata):
761 if 'zypp' == creatoropts['pkgmgr']:
763 ks = creatoropts['ks']
765 for repo in repometadata:
766 """ Mustn't replace group with package list if repo is ready for the corresponding package manager """
767 if iszypp and repo["patterns"]:
769 if not iszypp and repo["comps"]:
773 But we also must handle such cases, use zypp but repo only has comps,
774 use yum but repo only has patterns, use zypp but use_comps is true,
775 use yum but use_comps is false.
778 if iszypp and repo["comps"]:
779 groupfile = repo["comps"]
780 get_pkglist_handler = misc.get_pkglist_in_comps
781 if not iszypp and repo["patterns"]:
782 groupfile = repo["patterns"]
783 get_pkglist_handler = misc.get_pkglist_in_patterns
788 if i >= len(ks.handler.packages.groupList):
790 pkglist = get_pkglist_handler(ks.handler.packages.groupList[i].name, groupfile)
792 del ks.handler.packages.groupList[i]
794 if pkg not in ks.handler.packages.packageList:
795 ks.handler.packages.packageList.append(pkg)