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
39 import custom_commands.partition as partition
41 def read_kickstart(path):
42 """Parse a kickstart file and return a KickstartParser instance.
44 This is a simple utility function which takes a path to a kickstart file,
45 parses it and returns a pykickstart KickstartParser instance which can
46 be then passed to an ImageCreator constructor.
48 If an error occurs, a CreatorError exception is thrown.
51 #version = ksversion.makeVersion()
52 #ks = ksparser.KickstartParser(version)
54 using_version = ksversion.DEVEL
55 commandMap[using_version]["desktop"] = desktop.Moblin_Desktop
56 commandMap[using_version]["repo"] = moblinrepo.Moblin_Repo
57 commandMap[using_version]["bootloader"] = micboot.Moblin_Bootloader
58 commandMap[using_version]["part"] = partition.MeeGo_Partition
59 commandMap[using_version]["partition"] = partition.MeeGo_Partition
60 dataMap[using_version]["RepoData"] = moblinrepo.Moblin_RepoData
61 dataMap[using_version]["PartData"] = partition.MeeGo_PartData
62 superclass = ksversion.returnClassForVersion(version=using_version)
64 class KSHandlers(superclass):
65 def __init__(self, mapping={}):
66 superclass.__init__(self, mapping=commandMap[using_version])
68 ks = ksparser.KickstartParser(KSHandlers())
71 ks.readKickstart(path)
72 except kserrors.KickstartParseError, e:
73 msgptn = re.compile("^\D*(\d+).*(Section does not end with.*)$", re.S)
74 m = msgptn.match(str(e))
78 msger.warning("'%s:%s': %s" % (path, lineno, wrnmsg))
80 raise errors.KsError("'%s': %s" % (path, str(e)))
81 except kserrors.KickstartError, e:
82 raise errors.KsError("'%s': %s" % (path, str(e)))
85 def build_name(kscfg, prefix = None, suffix = None, maxlen = None):
86 """Construct and return an image name string.
88 This is a utility function to help create sensible name and fslabel
89 strings. The name is constructed using the sans-prefix-and-extension
90 kickstart filename and the supplied prefix and suffix.
92 If the name exceeds the maxlen length supplied, the prefix is first dropped
93 and then the kickstart filename portion is reduced until it fits. In other
94 words, the suffix takes precedence over the kickstart portion and the
95 kickstart portion takes precedence over the prefix.
97 kscfg -- a path to a kickstart file
98 prefix -- a prefix to prepend to the name; defaults to None, which causes
100 suffix -- a suffix to append to the name; defaults to None, which causes
101 a YYYYMMDDHHMM suffix to be used
102 maxlen -- the maximum length for the returned string; defaults to None,
103 which means there is no restriction on the name length
105 Note, if maxlen is less then the len(suffix), you get to keep both pieces.
108 name = os.path.basename(kscfg)
109 idx = name.rfind('.')
116 suffix = time.strftime("%Y%m%d%H%M")
118 if name.startswith(prefix):
119 name = name[len(prefix):]
121 ret = prefix + name + "-" + suffix
122 if not maxlen is None and len(ret) > maxlen:
123 ret = name[:maxlen - len(suffix) - 1] + "-" + suffix
127 class KickstartConfig(object):
128 """A base class for applying kickstart configurations to a system."""
129 def __init__(self, instroot):
130 self.instroot = instroot
132 def path(self, subpath):
133 return self.instroot + subpath
135 def _check_sysconfig(self):
136 if not os.path.exists(self.path("/etc/sysconfig")):
137 fs.makedirs(self.path("/etc/sysconfig"))
140 os.chroot(self.instroot)
143 def call(self, args):
144 if not os.path.exists("%s/%s" %(self.instroot, args[0])):
145 msger.warning("%s/%s" %(self.instroot, args[0]))
146 raise errors.KsError("Unable to run %s!" %(args))
147 subprocess.call(args, preexec_fn = self.chroot)
152 class LanguageConfig(KickstartConfig):
153 """A class to apply a kickstart language configuration to a system."""
154 def apply(self, kslang):
155 self._check_sysconfig()
157 f = open(self.path("/etc/sysconfig/i18n"), "w+")
158 f.write("LANG=\"" + kslang.lang + "\"\n")
161 class KeyboardConfig(KickstartConfig):
162 """A class to apply a kickstart keyboard configuration to a system."""
163 def apply(self, kskeyboard):
166 # should this impact the X keyboard config too?
167 # or do we want to make X be able to do this mapping?
169 #k = rhpl.keyboard.Keyboard()
170 #if kskeyboard.keyboard:
171 # k.set(kskeyboard.keyboard)
172 #k.write(self.instroot)
175 class TimezoneConfig(KickstartConfig):
176 """A class to apply a kickstart timezone configuration to a system."""
177 def apply(self, kstimezone):
178 self._check_sysconfig()
179 tz = kstimezone.timezone or "America/New_York"
180 utc = str(kstimezone.isUtc)
182 f = open(self.path("/etc/sysconfig/clock"), "w+")
183 f.write("ZONE=\"" + tz + "\"\n")
184 f.write("UTC=" + utc + "\n")
186 tz_source = self.path("/usr/share/zoneinfo/%s" % (tz))
187 tz_dest = self.path("/etc/localtime")
189 shutil.copyfile(tz_source, tz_dest)
190 except (IOError, OSError), (errno, msg):
191 raise errors.KsError("Error copying timezone info from "
193 % (tz_source, tz_dest, msg))
196 class AuthConfig(KickstartConfig):
197 """A class to apply a kickstart authconfig configuration to a system."""
198 def apply(self, ksauthconfig):
199 auth = ksauthconfig.authconfig or "--useshadow --enablemd5"
200 args = ["/usr/share/authconfig/authconfig.py", "--update", "--nostart"]
201 self.call(args + auth.split())
203 class FirewallConfig(KickstartConfig):
204 """A class to apply a kickstart firewall configuration to a system."""
205 def apply(self, ksfirewall):
207 # FIXME: should handle the rest of the options
209 if not os.path.exists(self.path("/usr/sbin/lokkit")):
211 if ksfirewall.enabled:
214 status = "--disabled"
216 self.call(["/usr/sbin/lokkit",
217 "-f", "--quiet", "--nostart", status])
219 class RootPasswordConfig(KickstartConfig):
220 """A class to apply a kickstart root password configuration to a system."""
222 self.call(["/usr/bin/passwd", "-d", "root"])
224 def set_encrypted(self, password):
225 self.call(["/usr/sbin/usermod", "-p", password, "root"])
227 def set_unencrypted(self, password):
228 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
229 if not os.path.exists("%s/%s" %(self.instroot, p)):
230 raise errors.KsError("Unable to set unencrypted password due "
233 p1 = subprocess.Popen(["/bin/echo", "root:%s" %password],
234 stdout = subprocess.PIPE,
235 preexec_fn = self.chroot)
236 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
238 stdout = subprocess.PIPE,
239 preexec_fn = self.chroot)
242 def apply(self, ksrootpw):
243 if ksrootpw.isCrypted:
244 self.set_encrypted(ksrootpw.password)
245 elif ksrootpw.password != "":
246 self.set_unencrypted(ksrootpw.password)
250 class UserConfig(KickstartConfig):
251 def set_empty_passwd(self, user):
252 self.call(["/usr/bin/passwd", "-d", user])
254 def set_encrypted_passwd(self, user, password):
255 self.call(["/usr/sbin/usermod", "-p", "%s" % password, user])
257 def set_unencrypted_passwd(self, user, password):
258 for p in ("/bin/echo", "/usr/sbin/chpasswd"):
259 if not os.path.exists("%s/%s" %(self.instroot, p)):
260 raise errors.KsError("Unable to set unencrypted password due "
263 p1 = subprocess.Popen(["/bin/echo", "%s:%s" %(user, password)],
264 stdout = subprocess.PIPE,
265 preexec_fn = self.chroot)
266 p2 = subprocess.Popen(["/usr/sbin/chpasswd", "-m"],
268 stdout = subprocess.PIPE,
269 preexec_fn = self.chroot)
272 def addUser(self, userconfig):
273 args = [ "/usr/sbin/useradd" ]
274 if userconfig.groups:
275 args += [ "--groups", string.join(userconfig.groups, ",") ]
277 args.append(userconfig.name)
278 dev_null = os.open("/dev/null", os.O_WRONLY)
279 subprocess.call(args,
282 preexec_fn = self.chroot)
284 if userconfig.password not in (None, ""):
285 if userconfig.isCrypted:
286 self.set_encrypted_passwd(userconfig.name,
289 self.set_unencrypted_passwd(userconfig.name,
292 self.set_empty_passwd(userconfig.name)
294 raise errors.KsError("Invalid kickstart command: %s" \
295 % userconfig.__str__())
297 def apply(self, user):
298 for userconfig in user.userList:
300 self.addUser(userconfig)
304 class ServicesConfig(KickstartConfig):
305 """A class to apply a kickstart services configuration to a system."""
306 def apply(self, ksservices):
307 if not os.path.exists(self.path("/sbin/chkconfig")):
309 for s in ksservices.enabled:
310 self.call(["/sbin/chkconfig", s, "on"])
311 for s in ksservices.disabled:
312 self.call(["/sbin/chkconfig", s, "off"])
314 class XConfig(KickstartConfig):
315 """A class to apply a kickstart X configuration to a system."""
316 def apply(self, ksxconfig):
317 if ksxconfig.startX and os.path.exists(self.path("/etc/inittab")):
318 f = open(self.path("/etc/inittab"), "rw+")
320 buf = buf.replace("id:3:initdefault", "id:5:initdefault")
324 if ksxconfig.defaultdesktop:
325 self._check_sysconfig()
326 f = open(self.path("/etc/sysconfig/desktop"), "w")
327 f.write("DESKTOP="+ksxconfig.defaultdesktop+"\n")
330 class DesktopConfig(KickstartConfig):
331 """A class to apply a kickstart desktop configuration to a system."""
332 def apply(self, ksdesktop):
333 if ksdesktop.defaultdesktop:
334 self._check_sysconfig()
335 f = open(self.path("/etc/sysconfig/desktop"), "w")
336 f.write("DESKTOP="+ksdesktop.defaultdesktop+"\n")
338 if os.path.exists(self.path("/etc/gdm/custom.conf")):
339 f = open(self.path("/etc/skel/.dmrc"), "w")
340 f.write("[Desktop]\n")
341 f.write("Session="+ksdesktop.defaultdesktop.lower()+"\n")
343 if ksdesktop.session:
344 if os.path.exists(self.path("/etc/sysconfig/uxlaunch")):
345 f = open(self.path("/etc/sysconfig/uxlaunch"), "a+")
346 f.write("session="+ksdesktop.session.lower()+"\n")
348 if ksdesktop.autologinuser:
349 self._check_sysconfig()
350 f = open(self.path("/etc/sysconfig/desktop"), "a+")
351 f.write("AUTOLOGIN_USER=" + ksdesktop.autologinuser + "\n")
353 if os.path.exists(self.path("/etc/gdm/custom.conf")):
354 f = open(self.path("/etc/gdm/custom.conf"), "w")
355 f.write("[daemon]\n")
356 f.write("AutomaticLoginEnable=true\n")
357 f.write("AutomaticLogin=" + ksdesktop.autologinuser + "\n")
360 class MoblinRepoConfig(KickstartConfig):
361 """A class to apply a kickstart desktop configuration to a system."""
362 def __create_repo_section(self, repo, type, fd):
365 reposuffix = {"base":"", "debuginfo":"-debuginfo", "source":"-source"}
366 reponame = repo.name + reposuffix[type]
369 baseurl = repo.baseurl
371 mirrorlist = repo.mirrorlist
373 elif type == "debuginfo":
375 if repo.baseurl.endswith("/"):
376 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
378 baseurl = os.path.dirname(repo.baseurl)
382 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
383 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
384 mirrorlist += "debug" + "-" + variant
386 elif type == "source":
388 if repo.baseurl.endswith("/"):
389 baseurl = os.path.dirname(
391 os.path.dirname(repo.baseurl)))
393 baseurl = os.path.dirname(os.path.dirname(repo.baseurl))
397 variant = repo.mirrorlist[repo.mirrorlist.find("$"):]
398 mirrorlist = repo.mirrorlist[0:repo.mirrorlist.find("$")]
399 mirrorlist += "source" + "-" + variant
401 fd.write("[" + reponame + "]\n")
402 fd.write("name=" + reponame + "\n")
403 fd.write("failovermethod=priority\n")
405 fd.write("baseurl=" + baseurl + "\n")
407 fd.write("mirrorlist=" + mirrorlist + "\n")
408 """ Skip saving proxy settings """
410 # fd.write("proxy=" + repo.proxy + "\n")
411 #if repo.proxy_username:
412 # fd.write("proxy_username=" + repo.proxy_username + "\n")
413 #if repo.proxy_password:
414 # fd.write("proxy_password=" + repo.proxy_password + "\n")
416 fd.write("gpgkey=" + repo.gpgkey + "\n")
417 fd.write("gpgcheck=1\n")
419 fd.write("gpgcheck=0\n")
420 if type == "source" or type == "debuginfo" or repo.disable:
421 fd.write("enabled=0\n")
423 fd.write("enabled=1\n")
426 def __create_repo_file(self, repo, repodir):
427 fs.makedirs(self.path(repodir))
428 f = open(self.path(repodir + "/" + repo.name + ".repo"), "w")
429 self.__create_repo_section(repo, "base", f)
431 self.__create_repo_section(repo, "debuginfo", f)
433 self.__create_repo_section(repo, "source", f)
436 def apply(self, ksrepo, repodata):
437 for repo in ksrepo.repoList:
439 #self.__create_repo_file(repo, "/etc/yum.repos.d")
440 self.__create_repo_file(repo, "/etc/zypp/repos.d")
441 """ Import repo gpg keys """
443 for repo in repodata:
446 "--root=%s" % self.instroot,
450 class RPMMacroConfig(KickstartConfig):
451 """A class to apply the specified rpm macros to the filesystem"""
455 if not os.path.exists(self.path("/etc/rpm")):
456 os.mkdir(self.path("/etc/rpm"))
457 f = open(self.path("/etc/rpm/macros.imgcreate"), "w+")
459 f.write("%_excludedocs 1\n")
460 f.write("%__file_context_path %{nil}\n")
461 if inst_langs(ks) != None:
462 f.write("%_install_langs ")
463 f.write(inst_langs(ks))
467 class NetworkConfig(KickstartConfig):
468 """A class to apply a kickstart network configuration to a system."""
469 def write_ifcfg(self, network):
470 p = self.path("/etc/sysconfig/network-scripts/ifcfg-" + network.device)
475 f.write("DEVICE=%s\n" % network.device)
476 f.write("BOOTPROTO=%s\n" % network.bootProto)
478 if network.bootProto.lower() == "static":
480 f.write("IPADDR=%s\n" % network.ip)
482 f.write("NETMASK=%s\n" % network.netmask)
485 f.write("ONBOOT=on\n")
487 f.write("ONBOOT=off\n")
490 f.write("ESSID=%s\n" % network.essid)
493 if network.ethtool.find("autoneg") == -1:
494 network.ethtool = "autoneg off " + network.ethtool
495 f.write("ETHTOOL_OPTS=%s\n" % network.ethtool)
497 if network.bootProto.lower() == "dhcp":
499 f.write("DHCP_HOSTNAME=%s\n" % network.hostname)
500 if network.dhcpclass:
501 f.write("DHCP_CLASSID=%s\n" % network.dhcpclass)
504 f.write("MTU=%s\n" % network.mtu)
508 def write_wepkey(self, network):
509 if not network.wepkey:
512 p = self.path("/etc/sysconfig/network-scripts/keys-" + network.device)
515 f.write("KEY=%s\n" % network.wepkey)
518 def write_sysconfig(self, useipv6, hostname, gateway):
519 path = self.path("/etc/sysconfig/network")
523 f.write("NETWORKING=yes\n")
526 f.write("NETWORKING_IPV6=yes\n")
528 f.write("NETWORKING_IPV6=no\n")
531 f.write("HOSTNAME=%s\n" % hostname)
533 f.write("HOSTNAME=localhost.localdomain\n")
536 f.write("GATEWAY=%s\n" % gateway)
540 def write_hosts(self, hostname):
542 if hostname and hostname != "localhost.localdomain":
543 localline += hostname + " "
544 l = hostname.split(".")
546 localline += l[0] + " "
547 localline += "localhost.localdomain localhost"
549 path = self.path("/etc/hosts")
552 f.write("127.0.0.1\t\t%s\n" % localline)
553 f.write("::1\t\tlocalhost6.localdomain6 localhost6\n")
556 def write_resolv(self, nodns, nameservers):
557 if nodns or not nameservers:
560 path = self.path("/etc/resolv.conf")
564 for ns in (nameservers):
566 f.write("nameserver %s\n" % ns)
570 def apply(self, ksnet):
571 fs.makedirs(self.path("/etc/sysconfig/network-scripts"))
579 for network in ksnet.network:
580 if not network.device:
581 raise errors.KsError("No --device specified with "
582 "network kickstart command")
584 if (network.onboot and network.bootProto.lower() != "dhcp" and
585 not (network.ip and network.netmask)):
586 raise errors.KsError("No IP address and/or netmask "
587 "specified with static "
588 "configuration for '%s'" %
591 self.write_ifcfg(network)
592 self.write_wepkey(network)
600 hostname = network.hostname
602 gateway = network.gateway
604 if network.nameserver:
605 nameservers = network.nameserver.split(",")
607 self.write_sysconfig(useipv6, hostname, gateway)
608 self.write_hosts(hostname)
609 self.write_resolv(nodns, nameservers)
612 def get_image_size(ks, default = None):
614 for p in ks.handler.partition.partitions:
615 if p.mountpoint == "/" and p.size:
618 return int(__size) * 1024L * 1024L
622 def get_image_fstype(ks, default = None):
623 for p in ks.handler.partition.partitions:
624 if p.mountpoint == "/" and p.fstype:
628 def get_image_fsopts(ks, default = None):
629 for p in ks.handler.partition.partitions:
630 if p.mountpoint == "/" and p.fsopts:
636 if isinstance(ks.handler.device, kscommands.device.FC3_Device):
637 devices.append(ks.handler.device)
639 devices.extend(ks.handler.device.deviceList)
642 for device in devices:
643 if not device.moduleName:
645 modules.extend(device.moduleName.split(":"))
649 def get_timeout(ks, default = None):
650 if not hasattr(ks.handler.bootloader, "timeout"):
652 if ks.handler.bootloader.timeout is None:
654 return int(ks.handler.bootloader.timeout)
656 def get_kernel_args(ks, default = "ro liveimg"):
657 if not hasattr(ks.handler.bootloader, "appendLine"):
659 if ks.handler.bootloader.appendLine is None:
661 return "%s %s" %(default, ks.handler.bootloader.appendLine)
663 def get_menu_args(ks, default = ""):
664 if not hasattr(ks.handler.bootloader, "menus"):
666 if ks.handler.bootloader.menus in (None, ""):
668 return "%s" % ks.handler.bootloader.menus
670 def get_default_kernel(ks, default = None):
671 if not hasattr(ks.handler.bootloader, "default"):
673 if not ks.handler.bootloader.default:
675 return ks.handler.bootloader.default
677 def get_repos(ks, repo_urls = {}):
679 for repo in ks.handler.repo.repoList:
681 if hasattr(repo, "includepkgs"):
682 inc.extend(repo.includepkgs)
685 if hasattr(repo, "excludepkgs"):
686 exc.extend(repo.excludepkgs)
688 baseurl = repo.baseurl
689 mirrorlist = repo.mirrorlist
691 if repo.name in repo_urls:
692 baseurl = repo_urls[repo.name]
695 if repos.has_key(repo.name):
696 msger.warning("Overriding already specified repo %s" %(repo.name,))
699 if hasattr(repo, "proxy"):
701 proxy_username = None
702 if hasattr(repo, "proxy_username"):
703 proxy_username = repo.proxy_username
704 proxy_password = None
705 if hasattr(repo, "proxy_password"):
706 proxy_password = repo.proxy_password
707 if hasattr(repo, "debuginfo"):
708 debuginfo = repo.debuginfo
709 if hasattr(repo, "source"):
711 if hasattr(repo, "gpgkey"):
713 if hasattr(repo, "disable"):
714 disable = repo.disable
716 if hasattr(repo, "ssl_verify"):
717 ssl_verify = repo.ssl_verify == "yes"
719 if hasattr(repo, "cost"):
722 if hasattr(repo, "priority"):
723 priority = repo.priority
725 repos[repo.name] = (repo.name, baseurl, mirrorlist, inc, exc,
726 proxy, proxy_username, proxy_password, debuginfo,
727 source, gpgkey, disable, ssl_verify, cost, priority)
729 return repos.values()
731 def convert_method_to_repo(ks):
733 ks.handler.repo.methodToRepo()
734 except (AttributeError, kserrors.KickstartError):
737 def get_packages(ks, required = []):
738 return ks.handler.packages.packageList + required
740 def get_groups(ks, required = []):
741 return ks.handler.packages.groupList + required
743 def get_excluded(ks, required = []):
744 return ks.handler.packages.excludedList + required
746 def get_partitions(ks, required = []):
747 return ks.handler.partition.partitions
749 def ignore_missing(ks):
750 return ks.handler.packages.handleMissing == ksconstants.KS_MISSING_IGNORE
752 def exclude_docs(ks):
753 return ks.handler.packages.excludeDocs
756 if hasattr(ks.handler.packages, "instLange"):
757 return ks.handler.packages.instLange
758 elif hasattr(ks.handler.packages, "instLangs"):
759 return ks.handler.packages.instLangs
762 def get_post_scripts(ks):
764 for s in ks.handler.scripts:
765 if s.type != ksparser.KS_SCRIPT_POST:
770 def add_repo(ks, repostr):
771 args = repostr.split()
772 repoobj = ks.handler.repo.parse(args[1:])
773 if repoobj and repoobj not in ks.handler.repo.repoList:
774 ks.handler.repo.repoList.append(repoobj)
776 def remove_all_repos(ks):
777 while len(ks.handler.repo.repoList) != 0:
778 del ks.handler.repo.repoList[0]
780 def remove_duplicate_repos(ks):
784 if len(ks.handler.repo.repoList) < 2:
786 if i >= len(ks.handler.repo.repoList) - 1:
788 name = ks.handler.repo.repoList[i].name
789 baseurl = ks.handler.repo.repoList[i].baseurl
790 if j < len(ks.handler.repo.repoList):
791 if (ks.handler.repo.repoList[j].name == name or \
792 ks.handler.repo.repoList[j].baseurl == baseurl):
793 del ks.handler.repo.repoList[j]
796 if j >= len(ks.handler.repo.repoList):
803 def resolve_groups(creatoropts, repometadata):
805 if 'zypp' == creatoropts['pkgmgr']:
807 ks = creatoropts['ks']
809 for repo in repometadata:
810 """ Mustn't replace group with package list if repo is ready for the
811 corresponding package manager.
814 if iszypp and repo["patterns"]:
816 if not iszypp and repo["comps"]:
819 # But we also must handle such cases, use zypp but repo only has comps,
820 # use yum but repo only has patterns, use zypp but use_comps is true,
821 # use yum but use_comps is false.
823 if iszypp and repo["comps"]:
824 groupfile = repo["comps"]
825 get_pkglist_handler = misc.get_pkglist_in_comps
826 if not iszypp and repo["patterns"]:
827 groupfile = repo["patterns"]
828 get_pkglist_handler = misc.get_pkglist_in_patterns
833 if i >= len(ks.handler.packages.groupList):
835 pkglist = get_pkglist_handler(
836 ks.handler.packages.groupList[i].name,
839 del ks.handler.packages.groupList[i]
841 if pkg not in ks.handler.packages.packageList:
842 ks.handler.packages.packageList.append(pkg)