def _miss(self, exn): if self.data.packages.handleMissing == constants.KS_MISSING_IGNORE: return log.error('Missed: %r', exn) if errors.errorHandler.cb(exn, str(exn)) == errors.ERROR_RAISE: # The progress bar polls kind of slowly, thus installation could # still continue for a bit before the quit message is processed. # Doing a sys.exit also ensures the running thread quits before # it can do anything else. progressQ.send_quit(1) sys.exit(1)
def _miss(self, exn): if self.data.packages.handleMissing == KS_MISSING_IGNORE: return log.error('Missed: %r', exn) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: # The progress bar polls kind of slowly, thus installation could # still continue for a bit before the quit message is processed. # Doing a sys.exit also ensures the running thread quits before # it can do anything else. progressQ.send_quit(1) pyanaconda.iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1)
def _handleMissing(self, exn): log.info("%s %s" % (self.__class__.__name__, inspect.stack()[0][3])) if self.data.packages.handleMissing == KS_MISSING_IGNORE: return # If we're doing non-interactive ks install, raise CmdlineError, # otherwise the system will just reboot automatically if flags.automatedInstall and not flags.ksprompt: errtxt = _("CmdlineError: Missing package: %s") % str(exn) log.error(errtxt) raise CmdlineError(errtxt) elif errorHandler.cb(exn) == ERROR_RAISE: # The progress bar polls kind of slowly, thus installation could # still continue for a bit before the quit message is processed. # Let's sleep forever to prevent any further actions and wait for # the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000)
def install(self): mainctx = create_new_context() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") gi.require_version("RpmOstree", "1.0") from gi.repository import OSTree, RpmOstree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safe_exec_with_redirect("ostree", [ "admin", "--sysroot=" + conf.target.physical_root, "init-fs", conf.target.physical_root ]) # Here, we use the physical root as sysroot, because we haven't # yet made a deployment. sysroot_file = Gio.File.new_for_path(conf.target.physical_root) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = Variant('b', False) if not conf.payload.verify_ssl: self._remoteOptions['tls-permissive'] = Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, Variant('a{sv}', self._remoteOptions), cancellable) # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(ostreesetup.ref) progressQ.send_message( _("Starting pull of %(branchName)s from %(source)s") % { "branchName": ref, "source": ostreesetup.remote }) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pull_progress_cb) pull_opts = {'refs': Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = Variant('as', [path]) break try: repo.pull_with_options(ostreesetup.remote, Variant('a{sv}', pull_opts), progress, cancellable) except GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) log.info("ostree pull: %s", progress.get_status() or "") progressQ.send_message(_("Preparing deployment of %s") % (ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safe_exec_with_redirect("ostree", [ "admin", "--sysroot=" + conf.target.physical_root, "os-init", ostreesetup.osname ]) admin_deploy_args = [ "admin", "--sysroot=" + conf.target.physical_root, "deploy", "--os=" + ostreesetup.osname ] admin_deploy_args.append(ostreesetup.remote + ':' + ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ref, )) self._safe_exec_with_redirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) util.set_system_root(deployment_path.get_path()) try: self._copy_bootloader_data() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) mainctx.pop_thread_default()
def install(self): mainctx = GLib.MainContext.new() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") from gi.repository import OSTree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safeExecWithRedirect("ostree", [ "admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "init-fs", iutil.getTargetPhysicalRoot() ]) self._sysroot_path = Gio.File.new_for_path( iutil.getTargetPhysicalRoot()) sysroot = OSTree.Sysroot.new(self._sysroot_path) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = GLib.Variant('b', False) if flags.noverifyssl: self._remoteOptions['tls-permissive'] = GLib.Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, GLib.Variant('a{sv}', self._remoteOptions), cancellable) progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % \ {"branchName": ostreesetup.ref, "source": ostreesetup.remote}) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pullProgressCb) try: repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable) except GLib.GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1) progressQ.send_message( _("Preparing deployment of %s") % (ostreesetup.ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safeExecWithRedirect("ostree", [ "admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "os-init", ostreesetup.osname ]) admin_deploy_args = [ "admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "deploy", "--os=" + ostreesetup.osname ] admin_deploy_args.append(ostreesetup.remote + ':' + ostreesetup.ref) log.info("ostree admin deploy starting") progressQ.send_message( _("Deployment starting: %s") % (ostreesetup.ref, )) self._safeExecWithRedirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message( _("Deployment complete: %s") % (ostreesetup.ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) iutil.setSysroot(deployment_path.get_path()) try: self._copyBootloaderData() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1) mainctx.pop_thread_default()
def _failure_limbo(): progressQ.send_quit(1) while True: time.sleep(10000)
def install(self): mainctx = GLib.MainContext.new() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") from gi.repository import OSTree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "init-fs", iutil.getTargetPhysicalRoot()]) self._sysroot_path = Gio.File.new_for_path(iutil.getTargetPhysicalRoot()) sysroot = OSTree.Sysroot.new(self._sysroot_path) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = GLib.Variant('b', False) if flags.noverifyssl: self._remoteOptions['tls-permissive'] = GLib.Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, GLib.Variant('a{sv}', self._remoteOptions), cancellable) progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % \ {"branchName": ostreesetup.ref, "source": ostreesetup.remote}) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pullProgressCb) try: repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable) except GLib.GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1) progressQ.send_message(_("Preparing deployment of %s") % (ostreesetup.ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "os-init", ostreesetup.osname]) admin_deploy_args = ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "deploy", "--os=" + ostreesetup.osname] admin_deploy_args.append(ostreesetup.remote + ':' + ostreesetup.ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ostreesetup.ref, )) self._safeExecWithRedirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ostreesetup.ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) iutil.setSysroot(deployment_path.get_path()) try: self._copyBootloaderData() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_report(constants.IPMI_ABORTED) sys.exit(1) mainctx.pop_thread_default()
def install(self): mainctx = GLib.MainContext.new() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") gi.require_version("RpmOstree", "1.0") from gi.repository import OSTree, RpmOstree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "init-fs", iutil.getTargetPhysicalRoot()]) # Here, we use the physical root as sysroot, because we haven't # yet made a deployment. sysroot_file = Gio.File.new_for_path(iutil.getTargetPhysicalRoot()) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = GLib.Variant('b', False) if flags.noverifyssl: self._remoteOptions['tls-permissive'] = GLib.Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, GLib.Variant('a{sv}', self._remoteOptions), cancellable) # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(ostreesetup.ref) progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % \ {"branchName": ref, "source": ostreesetup.remote}) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pullProgressCb) pull_opts = {'refs': GLib.Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = GLib.Variant('as', [path]) break try: repo.pull_with_options(ostreesetup.remote, GLib.Variant('a{sv}', pull_opts), progress, cancellable) except GLib.GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_abort(scripts=self.data.scripts) sys.exit(1) log.info("ostree pull: " + (progress.get_status() or "")) progressQ.send_message(_("Preparing deployment of %s") % (ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "os-init", ostreesetup.osname]) admin_deploy_args = ["admin", "--sysroot=" + iutil.getTargetPhysicalRoot(), "deploy", "--os=" + ostreesetup.osname] admin_deploy_args.append(ostreesetup.remote + ':' + ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ref, )) self._safeExecWithRedirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) iutil.setSysroot(deployment_path.get_path()) try: self._copyBootloaderData() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) iutil.ipmi_abort(scripts=self.data.scripts) sys.exit(1) mainctx.pop_thread_default()
def setup(self, storage, ksdata, instclass, payload): """ The setup method that should make changes to the runtime environment according to the data stored in this object. :param storage: object storing storage-related information (disks, partitioning, bootloader, etc.) :type storage: blivet.Blivet instance :param ksdata: data parsed from the kickstart file and set in the installation process :type ksdata: pykickstart.base.BaseHandler instance :param instclass: distribution-specific information :type instclass: pyanaconda.installclass.BaseInstallClass """ if self.dry_run or not self.profile_id: # nothing more to be done in the dry-run mode or if no profile is # selected return if not os.path.exists(self.preinst_content_path) and not os.path.exists(self.raw_preinst_content_path): # content not available/fetched yet try: self._fetch_content_and_initialize() except (common.OSCAPaddonError, data_fetch.DataFetchError) as e: log.error("Failed to fetch and initialize SCAP content!") msg = _("There was an error fetching and loading the security content:\n" + "%s\n" + "The installation should be aborted. Do you wish to continue anyway?") % e if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart # installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and # wait for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # check fingerprint if given if self.fingerprint: hash_obj = utils.get_hashing_algorithm(self.fingerprint) digest = utils.get_file_fingerprint(self.raw_preinst_content_path, hash_obj) if digest != self.fingerprint: log.error("Failed to fetch and initialize SCAP content!") msg = _("The integrity check of the security content failed.\n" + "The installation should be aborted. Do you wish to continue anyway?") if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart # installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and # wait for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # evaluate rules, do automatic fixes and stop if something that cannot # be fixed automatically is wrong fatal_messages = [message for message in self.rule_data.eval_rules(ksdata, storage) if message.type == common.MESSAGE_TYPE_FATAL] if any(fatal_messages): msg = "Wrong configuration detected!\n" msg += "\n".join(message.text for message in fatal_messages) msg += "\nThe installation should be aborted. Do you wish to continue anyway?" if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and wait # for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # add packages needed on the target system to the list of packages # that are requested to be installed pkgs_to_install = list(REQUIRED_PACKAGES) if self.content_type == "scap-security-guide": pkgs_to_install.append("scap-security-guide") for pkg in pkgs_to_install: if pkg not in ksdata.packages.packageList: ksdata.packages.packageList.append(pkg)
def setup(self, storage, ksdata, payload): """ The setup method that should make changes to the runtime environment according to the data stored in this object. :param storage: object storing storage-related information (disks, partitioning, bootloader, etc.) :type storage: blivet.Blivet instance :param ksdata: data parsed from the kickstart file and set in the installation process :type ksdata: pykickstart.base.BaseHandler instance """ if self.dry_run or not self.profile_id: # nothing more to be done in the dry-run mode or if no profile is # selected return if not os.path.exists(self.preinst_content_path) and not os.path.exists(self.raw_preinst_content_path): # content not available/fetched yet try: self._fetch_content_and_initialize() except (common.OSCAPaddonError, data_fetch.DataFetchError) as e: log.error("Failed to fetch and initialize SCAP content!") msg = _("There was an error fetching and loading the security content:\n" + "%s\n" + "The installation should be aborted. Do you wish to continue anyway?") % e if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart # installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and # wait for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # check fingerprint if given if self.fingerprint: hash_obj = utils.get_hashing_algorithm(self.fingerprint) digest = utils.get_file_fingerprint(self.raw_preinst_content_path, hash_obj) if digest != self.fingerprint: log.error("Failed to fetch and initialize SCAP content!") msg = _("The integrity check of the security content failed.\n" + "The installation should be aborted. Do you wish to continue anyway?") if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart # installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and # wait for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # evaluate rules, do automatic fixes and stop if something that cannot # be fixed automatically is wrong fatal_messages = [message for message in self.rule_data.eval_rules(ksdata, storage) if message.type == common.MESSAGE_TYPE_FATAL] if any(fatal_messages): msg = "Wrong configuration detected!\n" msg += "\n".join(message.text for message in fatal_messages) msg += "\nThe installation should be aborted. Do you wish to continue anyway?" if flags.flags.automatedInstall and not flags.flags.ksprompt: # cannot have ask in a non-interactive kickstart installation raise errors.CmdlineError(msg) answ = errors.errorHandler.ui.showYesNoQuestion(msg) if answ == errors.ERROR_CONTINUE: # prevent any futher actions here by switching to the dry # run mode and let things go on self.dry_run = True return else: # Let's sleep forever to prevent any further actions and wait # for the main thread to quit the process. progressQ.send_quit(1) while True: time.sleep(100000) # add packages needed on the target system to the list of packages # that are requested to be installed pkgs_to_install = list(REQUIRED_PACKAGES) if self.content_type == "scap-security-guide": pkgs_to_install.append("scap-security-guide") for pkg in pkgs_to_install: if pkg not in ksdata.packages.packageList: ksdata.packages.packageList.append(pkg)