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.
26 from mic.utils import errors
27 from mic.utils import misc
28 from mic.utils import fs_related as fs
31 sys.path.insert(0, os.path.dirname(__file__) or '.')
34 import pykickstart.commands as kscommands
35 import pykickstart.constants as ksconstants
36 import pykickstart.errors as kserrors
37 import pykickstart.parser as ksparser
38 import pykickstart.version as ksversion
39 from pykickstart.handlers.control import commandMap
40 from pykickstart.handlers.control import dataMap
42 import custom_commands.desktop as desktop
43 import custom_commands.moblinrepo as moblinrepo
44 import custom_commands.micboot as micboot
46 def read_kickstart(path):
47 """Parse a kickstart file and return a KickstartParser instance.
49 This is a simple utility function which takes a path to a kickstart file,
50 parses it and returns a pykickstart KickstartParser instance which can
51 be then passed to an ImageCreator constructor.
53 If an error occurs, a CreatorError exception is thrown.
56 #version = ksversion.makeVersion()
57 #ks = ksparser.KickstartParser(version)
59 using_version = ksversion.DEVEL
60 commandMap[using_version]["desktop"] = desktop.Moblin_Desktop
61 commandMap[using_version]["repo"] = moblinrepo.Moblin_Repo
62 commandMap[using_version]["bootloader"] = micboot.Moblin_Bootloader
63 dataMap[using_version]["RepoData"] = moblinrepo.Moblin_RepoData
64 superclass = ksversion.returnClassForVersion(version=using_version)
66 class KSHandlers(superclass):
67 def __init__(self, mapping={}):
68 superclass.__init__(self, mapping=commandMap[using_version])
70 ks = ksparser.KickstartParser(KSHandlers())
73 ks.readKickstart(path)
74 except IOError, (err, msg):
75 raise errors.KickstartError("Failed to read kickstart file "
76 "'%s' : %s" % (path, msg))
77 except kserrors.KickstartError, e:
78 raise errors.KickstartError("Failed to parse kickstart file "
79 "'%s' : %s" % (path, e))
82 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
83 """Construct and return an image name string.
85 This is a utility function to help create sensible name and fslabel
86 strings. The name is constructed using the sans-prefix-and-extension
87 kickstart filename and the supplied prefix and suffix.
89 If the name exceeds the maxlen length supplied, the prefix is first dropped
90 and then the kickstart filename portion is reduced until it fits. In other
91 words, the suffix takes precedence over the kickstart portion and the
92 kickstart portion takes precedence over the prefix.
94 kscfg -- a path to a kickstart file
95 prefix -- a prefix to prepend to the name; defaults to None, which causes
97 suffix -- a suffix to append to the name; defaults to None, which causes
98 a YYYYMMDDHHMM suffix to be used
99 maxlen -- the maximum length for the returned string; defaults to None,
100 which means there is no restriction on the name length
102 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
105 name = os.path.basename(kscfg)
106 idx = name.rfind('.')
113 suffix = time.strftime("%Y%m%d%H%M")
115 if name.startswith(prefix):
116 name = name[len(prefix):]
118 ret = prefix + name + "-" + suffix
119 if not maxlen is None and len(ret) > maxlen:
120 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
124 class KickstartConfig(object):
125 """A base class for applying kickstart configurations to a system."""
126 def __init__(self, instroot):
127 self.instroot = instroot
129 def path(self, subpath):
130 return self.instroot + subpath
133 os.chroot(self.instroot)
136 def call(self, args):
137 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
138 msger.warning("%s/%s" %(self.instroot, args[0]))
139 raise errors.KickstartError("Unable to run %s!" %(args))
140 subprocess.call(args, preexec_fn = self.chroot)
145 class LanguageConfig(KickstartConfig):
146 """A class to apply a kickstart language configuration to a system."""
147 def apply(self, kslang):
148 lang = kslang.lang or "en_US.UTF-8"
150 f = open(self.path("/etc/sysconfig/i18n"), "w+")
151 f.write("LANG=\"" + lang + "\"\n")
154 class KeyboardConfig(KickstartConfig):
155 """A class to apply a kickstart keyboard configuration to a system."""
156 def apply(self, kskeyboard):
159 # should this impact the X keyboard config too?
160 # or do we want to make X be able to do this mapping?
162 #k = rhpl.keyboard.Keyboard()
163 #if kskeyboard.keyboard:
164 # k.set(kskeyboard.keyboard)
165 #k.write(self.instroot)
168 class TimezoneConfig(KickstartConfig):
169 """A class to apply a kickstart timezone configuration to a system."""
170 def apply(self, kstimezone):
171 tz = kstimezone.timezone or "America/New_York"
172 utc = str(kstimezone.isUtc)
174 f = open(self.path("/etc/sysconfig/clock"), "w+")
175 f.write("ZONE=\"" + tz + "\"\n")
176 f.write("UTC=" + utc + "\n")
179 shutil.copyfile(self.path("/usr/share/zoneinfo/%s" %(tz,)),
180 self.path("/etc/localtime"))
181 except (IOError, OSError), (errno, msg):
182 raise errors.KickstartError("Error copying timezone info: %s" %(msg,))
185 class AuthConfig(KickstartConfig):
186 """A class to apply a kickstart authconfig configuration to a system."""
187 def apply(self, ksauthconfig):
188 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
189 args = ["/usr/share/authconfig/authconfig.py", "--update", "--nostart"]
190 self.call(args + auth.split())
192 class FirewallConfig(KickstartConfig):
193 """A class to apply a kickstart firewall configuration to a system."""
194 def apply(self, ksfirewall):
196 # FIXME: should handle the rest of the options
198 if not os.path.exists(self.path("/usr/sbin/lokkit")):
200 if ksfirewall.enabled:
203 status = "--disabled"
205 self.call(["/usr/sbin/lokkit",
206 "-f", "--quiet", "--nostart", status])
208 class RootPasswordConfig(KickstartConfig):
209 """A class to apply a kickstart root password configuration to a system."""
211 self.call(["/usr/bin/passwd", "-d", "root"])
213 def set_encrypted(self, password):
214 self.call(["/usr/sbin/usermod", "-p", password, "root"])
216 def set_unencrypted(self, password):
217 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
218 if not os.path.exists("%s/%s" %(self.instroot, p)):
219 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
221 p1 = subprocess.Popen(["/bin/echo", "root:%s" %password],
222 stdout = subprocess.PIPE,
223 preexec_fn = self.chroot)
224 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
226 stdout = subprocess.PIPE,
227 preexec_fn = self.chroot)
230 def apply(self, ksrootpw):
231 if ksrootpw.isCrypted:
232 self.set_encrypted(ksrootpw.password)
233 elif ksrootpw.password != "":
234 self.set_unencrypted(ksrootpw.password)
238 class UserConfig(KickstartConfig):
239 def set_empty_passwd(self, user):
240 self.call(["/usr/bin/passwd", "-d", user])
242 def set_encrypted_passwd(self, user, password):
243 self.call(["/usr/sbin/usermod", "-p", "%s" % password, user])
245 def set_unencrypted_passwd(self, user, password):
246 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
247 if not os.path.exists("%s/%s" %(self.instroot, p)):
248 raise errors.KickstartError("Unable to set unencrypted password due to lack of %s" % p)
250 p1 = subprocess.Popen(["/bin/echo", "%s:%s" %(user, password)],
251 stdout = subprocess.PIPE,
252 preexec_fn = self.chroot)
253 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
255 stdout = subprocess.PIPE,
256 preexec_fn = self.chroot)
259 def addUser(self, userconfig):
260 args = [ "/usr/sbin/useradd" ]
261 if userconfig.groups:
262 args += [ "--groups", string.join(userconfig.groups, ",") ]
264 args.append(userconfig.name)
265 dev_null = os.open("/dev/null", os.O_WRONLY)
266 subprocess.call(args,
269 preexec_fn = self.chroot)
271 if userconfig.password not in (None, ""):
272 if userconfig.isCrypted:
273 self.set_encrypted_passwd(userconfig.name, userconfig.password)
275 self.set_unencrypted_passwd(userconfig.name, userconfig.password)
277 self.set_empty_passwd(userconfig.name)
279 raise errors.KickstartError("Invalid kickstart command: %s" % userconfig.__str__())
281 def apply(self, user):
282 for userconfig in user.userList:
284 self.addUser(userconfig)
288 class ServicesConfig(KickstartConfig):
289 """A class to apply a kickstart services configuration to a system."""
290 def apply(self, ksservices):
291 if not os.path.exists(self.path("/sbin/chkconfig")):
293 for s in ksservices.enabled:
294 self.call(["/sbin/chkconfig", s, "on"])
295 for s in ksservices.disabled:
296 self.call(["/sbin/chkconfig", s, "off"])
298 class XConfig(KickstartConfig):
299 """A class to apply a kickstart X configuration to a system."""
300 def apply(self, ksxconfig):
302 f = open(self.path("/etc/inittab"), "rw+")
304 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
308 if ksxconfig.defaultdesktop:
309 f = open(self.path("/etc/sysconfig/desktop"), "w")
310 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
313 class DesktopConfig(KickstartConfig):
314 """A class to apply a kickstart desktop configuration to a system."""
315 def apply(self, ksdesktop):
316 if ksdesktop.defaultdesktop:
317 f = open(self.path("/etc/sysconfig/desktop"), "w")
318 f.write("DESKTOP="+ksdesktop.defaultdesktop+"\n")
320 if os.path.exists(self.path("/etc/gdm/custom.conf")):
321 f = open(self.path("/etc/skel/.dmrc"), "w")
322 f.write("[Desktop]\n")
323 f.write("Session="+ksdesktop.defaultdesktop.lower()+"\n")
325 if ksdesktop.session:
326 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
327 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
328 f.write("session="+ksdesktop.session.lower()+"\n")
330 if ksdesktop.autologinuser:
331 f = open(self.path("/etc/sysconfig/desktop"), "a+")
332 f.write("AUTOLOGIN_USER=" + ksdesktop.autologinuser + "\n")
334 if ksdesktop.session:
335 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
336 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
337 f.write("user="+ksdesktop.autologinuser+"\n")
339 if os.path.exists(self.path("/etc/gdm/custom.conf")):
340 f = open(self.path("/etc/gdm/custom.conf"), "w")
341 f.write("[daemon]\n")
342 f.write("AutomaticLoginEnable=true\n")
343 f.write("AutomaticLogin=" + ksdesktop.autologinuser + "\n")
346 class MoblinRepoConfig(KickstartConfig):
347 """A class to apply a kickstart desktop configuration to a system."""
348 def __create_repo_section(self, repo, type, fd):
351 reposuffix = {"base":"", "debuginfo":"-debuginfo", "source":"-source"}
352 reponame = repo.name + reposuffix[type]
355 baseurl = repo.baseurl
357 mirrorlist = repo.mirrorlist
358 elif type == "debuginfo":
360 if repo.baseurl.endswith("/"):
361 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
363 baseurl = os.path.dirname(repo.baseurl)
366 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
367 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
368 mirrorlist += "debug" + "-" + variant
369 elif type == "source":
371 if repo.baseurl.endswith("/"):
372 baseurl = os.path.dirname(os.path.dirname(os.path.dirname(repo.baseurl)))
374 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
377 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
378 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
379 mirrorlist += "source" + "-" + variant
381 fd.write("[" + reponame + "]\n")
382 fd.write("name=" + reponame + "\n")
383 fd.write("failovermethod=priority\n")
385 fd.write("baseurl=" + baseurl + "\n")
387 fd.write("mirrorlist=" + mirrorlist + "\n")
388 """ Skip saving proxy settings """
390 # fd.write("proxy=" + repo.proxy + "\n")
391 #if repo.proxy_username:
392 # fd.write("proxy_username=" + repo.proxy_username + "\n")
393 #if repo.proxy_password:
394 # fd.write("proxy_password=" + repo.proxy_password + "\n")
396 fd.write("gpgkey=" + repo.gpgkey + "\n")
397 fd.write("gpgcheck=1\n")
399 fd.write("gpgcheck=0\n")
400 if type == "source" or type == "debuginfo" or repo.disable:
401 fd.write("enabled=0\n")
403 fd.write("enabled=1\n")
406 def __create_repo_file(self, repo, repodir):
407 if not os.path.exists(self.path(repodir)):
408 fs.makedirs(self.path(repodir))
409 f = open(self.path(repodir + "/" + repo.name + ".repo"), "w")
410 self.__create_repo_section(repo, "base", f)
412 self.__create_repo_section(repo, "debuginfo", f)
414 self.__create_repo_section(repo, "source", f)
417 def apply(self, ksrepo, repodata):
418 for repo in ksrepo.repoList:
420 #self.__create_repo_file(repo, "/etc/yum.repos.d")
421 self.__create_repo_file(repo, "/etc/zypp/repos.d")
422 """ Import repo gpg keys """
424 dev_null = os.open("/dev/null", os.O_WRONLY)
425 for repo in repodata:
427 subprocess.call([fs.find_binary_path("rpm"), "--root=%s" % self.instroot, "--import", repo['repokey']],
428 stdout = dev_null, stderr = dev_null)
431 class RPMMacroConfig(KickstartConfig):
432 """A class to apply the specified rpm macros to the filesystem"""
436 if not os.path.exists(self.path("/etc/rpm")):
437 os.mkdir(self.path("/etc/rpm"))
438 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
440 f.write("%_excludedocs 1\n")
441 f.write("%__file_context_path %{nil}\n")
442 if inst_langs(ks) != None:
443 f.write("%_install_langs ")
444 f.write(inst_langs(ks))
448 class NetworkConfig(KickstartConfig):
449 """A class to apply a kickstart network configuration to a system."""
450 def write_ifcfg(self, network):
451 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
456 f.write("DEVICE=%s\n" % network.device)
457 f.write("BOOTPROTO=%s\n" % network.bootProto)
459 if network.bootProto.lower() == "static":
461 f.write("IPADDR=%s\n" % network.ip)
463 f.write("NETMASK=%s\n" % network.netmask)
466 f.write("ONBOOT=on\n")
468 f.write("ONBOOT=off\n")
471 f.write("ESSID=%s\n" % network.essid)
474 if network.ethtool.find("autoneg") == -1:
475 network.ethtool = "autoneg off " + network.ethtool
476 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
478 if network.bootProto.lower() == "dhcp":
480 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
481 if network.dhcpclass:
482 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
485 f.write("MTU=%s\n" % network.mtu)
489 def write_wepkey(self, network):
490 if not network.wepkey:
493 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
496 f.write("KEY=%s\n" % network.wepkey)
499 def write_sysconfig(self, useipv6, hostname, gateway):
500 path = self.path("/etc/sysconfig/network")
504 f.write("NETWORKING=yes\n")
507 f.write("NETWORKING_IPV6=yes\n")
509 f.write("NETWORKING_IPV6=no\n")
512 f.write("HOSTNAME=%s\n" % hostname)
514 f.write("HOSTNAME=localhost.localdomain\n")
517 f.write("GATEWAY=%s\n" % gateway)
521 def write_hosts(self, hostname):
523 if hostname and hostname != "localhost.localdomain":
524 localline += hostname + " "
525 l = hostname.split(".")
527 localline += l[0] + " "
528 localline += "localhost.localdomain localhost"
530 path = self.path("/etc/hosts")
533 f.write("127.0.0.1\t\t%s\n" % localline)
534 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
537 def write_resolv(self, nodns, nameservers):
538 if nodns or not nameservers:
541 path = self.path("/etc/resolv.conf")
545 for ns in (nameservers):
547 f.write("nameserver %s\n" % ns)
551 def apply(self, ksnet):
552 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
560 for network in ksnet.network:
561 if not network.device:
562 raise errors.KickstartError("No --device specified with "
563 "network kickstart command")
565 if (network.onboot and network.bootProto.lower() != "dhcp" and
566 not (network.ip and network.netmask)):
567 raise errors.KickstartError("No IP address and/or netmask "
568 "specified with static "
569 "configuration for '%s'" %
572 self.write_ifcfg(network)
573 self.write_wepkey(network)
581 hostname = network.hostname
583 gateway = network.gateway
585 if network.nameserver:
586 nameservers = network.nameserver.split(",")
588 self.write_sysconfig(useipv6, hostname, gateway)
589 self.write_hosts(hostname)
590 self.write_resolv(nodns, nameservers)
593 def get_image_size(ks, default = None):
595 for p in ks.handler.partition.partitions:
596 if p.mountpoint == "/" and p.size:
599 return int(__size) * 1024L * 1024L
603 def get_image_fstype(ks, default = None):
604 for p in ks.handler.partition.partitions:
605 if p.mountpoint == "/" and p.fstype:
609 def get_image_fsopts(ks, default = None):
610 for p in ks.handler.partition.partitions:
611 if p.mountpoint == "/" and p.fsopts:
617 if isinstance(ks.handler.device, kscommands.device.FC3_Device):
618 devices.append(ks.handler.device)
620 devices.extend(ks.handler.device.deviceList)
623 for device in devices:
624 if not device.moduleName:
626 modules.extend(device.moduleName.split(":"))
630 def get_timeout(ks, default = None):
631 if not hasattr(ks.handler.bootloader, "timeout"):
633 if ks.handler.bootloader.timeout is None:
635 return int(ks.handler.bootloader.timeout)
637 def get_kernel_args(ks, default = "ro liveimg"):
638 if not hasattr(ks.handler.bootloader, "appendLine"):
640 if ks.handler.bootloader.appendLine is None:
642 return "%s %s" %(default, ks.handler.bootloader.appendLine)
644 def get_menu_args(ks, default = "liveinst"):
645 if not hasattr(ks.handler.bootloader, "menus"):
647 if ks.handler.bootloader.menus in (None, ""):
649 return "%s" % ks.handler.bootloader.menus
651 def get_default_kernel(ks, default = None):
652 if not hasattr(ks.handler.bootloader, "default"):
654 if not ks.handler.bootloader.default:
656 return ks.handler.bootloader.default
658 def get_repos(ks, repo_urls = {}):
660 for repo in ks.handler.repo.repoList:
662 if hasattr(repo, "includepkgs"):
663 inc.extend(repo.includepkgs)
666 if hasattr(repo, "excludepkgs"):
667 exc.extend(repo.excludepkgs)
669 baseurl = repo.baseurl
670 mirrorlist = repo.mirrorlist
672 if repo.name in repo_urls:
673 baseurl = repo_urls[repo.name]
676 if repos.has_key(repo.name):
677 logging.warn("Overriding already specified repo %s" %(repo.name,))
680 if hasattr(repo, "proxy"):
682 proxy_username = None
683 if hasattr(repo, "proxy_username"):
684 proxy_username = repo.proxy_username
685 proxy_password = None
686 if hasattr(repo, "proxy_password"):
687 proxy_password = repo.proxy_password
688 if hasattr(repo, "debuginfo"):
689 debuginfo = repo.debuginfo
690 if hasattr(repo, "source"):
692 if hasattr(repo, "gpgkey"):
694 if hasattr(repo, "disable"):
695 disable = repo.disable
697 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable)
699 return repos.values()
701 def convert_method_to_repo(ks):
703 ks.handler.repo.methodToRepo()
704 except (AttributeError, kserrors.KickstartError):
707 def get_packages(ks, required = []):
708 return ks.handler.packages.packageList + required
710 def get_groups(ks, required = []):
711 return ks.handler.packages.groupList + required
713 def get_excluded(ks, required = []):
714 return ks.handler.packages.excludedList + required
716 def get_partitions(ks, required = []):
717 return ks.handler.partition.partitions
719 def ignore_missing(ks):
720 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
722 def exclude_docs(ks):
723 return ks.handler.packages.excludeDocs
726 if hasattr(ks.handler.packages, "instLange"):
727 return ks.handler.packages.instLange
728 elif hasattr(ks.handler.packages, "instLangs"):
729 return ks.handler.packages.instLangs
732 def get_post_scripts(ks):
734 for s in ks.handler.scripts:
735 if s.type != ksparser.KS_SCRIPT_POST:
740 def add_repo(ks, repostr):
741 args = repostr.split()
742 repoobj = ks.handler.repo.parse(args[1:])
743 if repoobj and repoobj not in ks.handler.repo.repoList:
744 ks.handler.repo.repoList.append(repoobj)
746 def remove_all_repos(ks):
747 while len(ks.handler.repo.repoList) != 0:
748 del ks.handler.repo.repoList[0]
750 def remove_duplicate_repos(ks):
754 if len(ks.handler.repo.repoList) < 2:
756 if i >= len(ks.handler.repo.repoList) - 1:
758 name = ks.handler.repo.repoList[i].name
759 baseurl = ks.handler.repo.repoList[i].baseurl
760 if j < len(ks.handler.repo.repoList):
761 if (ks.handler.repo.repoList[j].name == name or \
762 ks.handler.repo.repoList[j].baseurl == baseurl):
763 del ks.handler.repo.repoList[j]
766 if j >= len(ks.handler.repo.repoList):
773 def resolve_groups(creator, repometadata, use_comps = False):
774 pkgmgr = creator.pkgmgr.get_default_pkg_manager
776 if creator.pkgmgr.managers.has_key("zypp") and creator.pkgmgr.managers['zypp'] == pkgmgr:
780 for repo in repometadata:
781 """ Mustn't replace group with package list if repo is ready for the corresponding package manager """
782 if iszypp and repo["patterns"] and not use_comps:
784 if not iszypp and repo["comps"] and use_comps:
788 But we also must handle such cases, use zypp but repo only has comps,
789 use yum but repo only has patterns, use zypp but use_comps is true,
790 use yum but use_comps is false.
794 if (use_comps and repo["comps"]) or (not repo["patterns"] and repo["comps"]):
795 groupfile = repo["comps"]
796 get_pkglist_handler = misc.get_pkglist_in_comps
798 if (not use_comps and repo["patterns"]) or (not repo["comps"] and repo["patterns"]):
799 groupfile = repo["patterns"]
800 get_pkglist_handler = misc.get_pkglist_in_patterns
805 if i >= len(ks.handler.packages.groupList):
807 pkglist = get_pkglist_handler(ks.handler.packages.groupList[i].name, groupfile)
809 del ks.handler.packages.groupList[i]
811 if pkg not in ks.handler.packages.packageList:
812 ks.handler.packages.packageList.append(pkg)