[PATCH 15/16] Add support for KS %addon section and the API+code to use it
Martin Sivak
msivak at redhat.com
Thu Dec 6 15:46:38 UTC 2012
---
anaconda | 3 +-
pyanaconda/addons.py | 142 ++++++++++++++++++++++++++++++++++++++++++++++
pyanaconda/constants.py | 5 +-
pyanaconda/install.py | 5 +-
pyanaconda/kickstart.py | 41 +++++++++++--
pyanaconda/ui/__init__.py | 8 +++
6 files changed, 197 insertions(+), 7 deletions(-)
create mode 100644 pyanaconda/addons.py
diff --git a/anaconda b/anaconda
index 0789094..01de6f8 100755
--- a/anaconda
+++ b/anaconda
@@ -32,6 +32,7 @@
import atexit, sys, os, re, time, subprocess
from tempfile import mkstemp
+from pyanaconda import constants
# keep up with process ID of the window manager if we start it
wm_pid = None
@@ -828,7 +829,7 @@ if __name__ == "__main__":
break
if not ksdata:
- ksdata = kickstart.AnacondaKSHandler()
+ ksdata = kickstart.AnacondaKSHandler(constants.addon_paths)
if ksdata.rescue.rescue:
anaconda.rescue = True
diff --git a/pyanaconda/addons.py b/pyanaconda/addons.py
new file mode 100644
index 0000000..0220107
--- /dev/null
+++ b/pyanaconda/addons.py
@@ -0,0 +1,142 @@
+# Methods and API for anaconda/firstboot 3rd party addons
+#
+# Copyright (C) 2012 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details. You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): Martin Sivak <msivak at redhat.com>
+#
+
+__all__ = ["AddonSection", "AddonRegistry", "AddonData", "collect_addon_paths"]
+
+import os
+from pykickstart.sections import Section
+
+def collect_addon_paths(toplevel_addon_paths, ui_subdir = "gui"):
+ """This method looks into the directories present
+ in toplevel_addon_paths and registers each subdirectory
+ as a new addonidentified by that subdirectory name.
+
+ It then registers spokes, categories and data (ks)
+ paths for the application to use. By default is looks
+ for spokes and categories in <addon>/gui/ subdirectory
+ but that can be changed using the ui_subdir argument."""
+
+ module_paths = {
+ "spokes": [],
+ "ks": [],
+ "categories": []
+ }
+
+ for path in toplevel_addon_paths:
+ try:
+ files = os.listdir(path)
+ except OSError:
+ files = []
+
+ for addon_id in files:
+ addon_ks_path = os.path.join(path, addon_id, "ks")
+ if os.path.isdir(addon_ks_path):
+ module_paths["ks"].append(("anaconda.addon.%s.ks.%%s" % addon_id, addon_ks_path))
+
+ addon_spoke_path = os.path.join(path, addon_id, ui_subdir, "spokes")
+ if os.path.isdir(addon_spoke_path):
+ module_paths["spokes"].append(("anaconda.addon.%s.spokes.%%s" % addon_id, addon_spoke_path))
+
+ addon_category_path = os.path.join(path, addon_id, ui_subdir, "categories")
+ if os.path.isdir(addon_spoke_path):
+ module_paths["categories"].append(("anaconda.addon.%s.categories.%%s" % addon_id, addon_category_path))
+
+ return module_paths
+
+class AddonRegistry(object):
+ """This class represents the ksdata.addons object and
+ maintains the ids and data structures for loaded
+ addons.
+
+ It acts as a proxy during kickstart save.
+ """
+
+ def __init__(self, dictionary):
+ self.__dict__ = dictionary
+
+ def __str__(self):
+ return reduce(lambda acc,(id, addon): acc + "%%addon %s\n%s%%end\n" % (id, str(addon)),
+ self.__dict__.iteritems(), "")
+
+ def execute(self, storage, ksdata, instClass):
+ """This method calls execute on all the registered addons."""
+ for k, v in self.__dict__.iteritems():
+ v.execute(storage, ksdata, instClass)
+
+class AddonData(object):
+ """This is a common parent class for loading and storing
+ 3rd party data to kickstart. It is instantiated by
+ kickstart parser and stored as ksdata.addons.<name>
+ to be used in the user interfaces.
+
+ The mandatory method handle_line receives all lines
+ from the corresponding addon section in kickstart and
+ the mandatory __str__ implementation is responsible for
+ returning the proper kickstart text (to be placed into
+ the %addon section) back.
+
+ There is also a mandatory method execute, which should
+ make all the described changes to the installed system.
+ """
+
+ def __init__(self, name):
+ self.name = name
+ self.content = ""
+
+ def __str__(self):
+ return self.content
+
+ def execute(self, storage, ksdata, instClass):
+ """Make the changes to the underlying system."""
+ pass
+
+ def handle_line(self, line):
+ """Process one kickstart line."""
+ self.content += line
+
+class AddonSection(Section):
+ sectionOpen = "%addon"
+
+ def __init__(self, *args, **kwargs):
+ Section.__init__(self, *args, **kwargs)
+ self.addon_id = None
+
+ def handleLine(self, line):
+ if not self.handler:
+ return
+
+ if not self.addon_id:
+ return
+
+ addon = getattr(self.handler.addon, self.addon_id)
+ addon.handle_line(line)
+
+ def handleHeader(self, lineno, args):
+ """Process the arguments to the %addon header."""
+ Section.handleHeader(self, lineno, args)
+ op = KSOptionParser(version=self.version)
+ (opts, extra) = op.parse_args(args=args[1:], lineno=lineno)
+ self.addon_id = extra[0]
+
+ # if the addon is not registered, create dummy placeholder for it
+ if self.addon_id and not hasattr(self.handler.addon, self.addon_id):
+ setattr(self.handler.addon, self.addon_id, AnacondaKSAddon(self.addon_id))
+
diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
index 7ff5020..5cc066c 100644
--- a/pyanaconda/constants.py
+++ b/pyanaconda/constants.py
@@ -38,6 +38,9 @@ MAX_PART_SIZE = 1024*1024*1024
# install key related constants
SKIP_KEY = -50
+# where to look for 3rd party addons
+addon_paths = ["/usr/share/anaconda/addons"]
+
# pull in kickstart constants as well
from pykickstart.constants import *
@@ -96,4 +99,4 @@ USEVNC = _("Start VNC")
USETEXT = _("Use text mode")
# Runlevel files
-RUNLEVELS = {3: 'multi-user.target', 5: 'graphical.target'}
\ No newline at end of file
+RUNLEVELS = {3: 'multi-user.target', 5: 'graphical.target'}
diff --git a/pyanaconda/install.py b/pyanaconda/install.py
index 6754a0a..4e552ea 100644
--- a/pyanaconda/install.py
+++ b/pyanaconda/install.py
@@ -52,7 +52,7 @@ def doConfiguration(storage, payload, ksdata, instClass):
from pyanaconda import progress
from pyanaconda.kickstart import runPostScripts
- progress.send_init(4)
+ progress.send_init(5)
# Now run the execute methods of ksdata that require an installed system
# to be present first.
@@ -79,6 +79,9 @@ def doConfiguration(storage, payload, ksdata, instClass):
ksdata.group.execute(storage, ksdata, instClass, u)
ksdata.user.execute(storage, ksdata, instClass, u)
+ with progress_report(_("Configuring addons")):
+ ksdata.addon.execute(storage, ksdata, instClass, u)
+
with progress_report(_("Running post install scripts")):
runPostScripts(ksdata.scripts)
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index 1ec294a..693ecd4 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -52,6 +52,8 @@ from pyanaconda import network
from pyanaconda.simpleconfig import SimpleConfigFile
from pyanaconda.users import getPassAlgo
from pyanaconda.desktop import Desktop
+from .ui.common import collect
+from .addons import AddonSection, AddonData, AddonRegistry
from pykickstart.base import KickstartCommand
from pykickstart.constants import *
@@ -78,6 +80,7 @@ packagesSeen = False
# so it needs to know about them in some additional way: have the topology ready.
topology = None
+
class AnacondaKSScript(KSScript):
def run(self, chroot):
if self.inChroot:
@@ -1342,11 +1345,38 @@ dataMap = {
superclass = returnClassForVersion()
+
class AnacondaKSHandler(superclass):
- def __init__ (self):
+ AddonClassType = AddonData
+
+ def __init__ (self, addon_paths = []):
superclass.__init__(self, commandUpdates=commandMap, dataUpdates=dataMap)
self.onPart = {}
+ # collect all kickstart addons for anaconda to addons dictionary
+ # which maps addon_id to it's own data structure based on BaseData
+ # with execute method
+ addons = {}
+
+ # collect all AddonData subclasses from
+ # for p in addon_paths: <p>/<plugin id>/ks/*.(py|so)
+ # and register them under <plugin id> name
+ for module_name, path in addon_paths:
+ addon_id = os.path.basename(os.path.dirname(os.path.abspath(path)))
+ if not os.path.isdir(path):
+ continue
+
+ classes = collect(module_name, path, lambda cls: issubclass(cls, self.AddonClassType))
+ print classes
+ if classes:
+ addons[addon_id] = classes[0](name = addon_id)
+
+ # Prepare the final structures for 3rd party addons
+ self.addon = AddonRegistry(addons)
+
+ def __str__(self):
+ return superclass.__str__(self) + "\n" + str(self.addon)
+
class AnacondaPreParser(KickstartParser):
# A subclass of KickstartParser that only looks for %pre scripts and
# sets them up to be run. All other scripts and commands are ignored.
@@ -1362,7 +1392,9 @@ class AnacondaPreParser(KickstartParser):
self.registerSection(NullSection(self.handler, sectionOpen="%post"))
self.registerSection(NullSection(self.handler, sectionOpen="%traceback"))
self.registerSection(NullSection(self.handler, sectionOpen="%packages"))
-
+ self.registerSection(NullSection(self.handler, sectionOpen="%addon"))
+
+
class AnacondaKSParser(KickstartParser):
def __init__ (self, handler, followIncludes=True, errorsAreFatal=True,
missingIncludeIsFatal=True, scriptClass=AnacondaKSScript):
@@ -1380,7 +1412,8 @@ class AnacondaKSParser(KickstartParser):
self.registerSection(PostScriptSection(self.handler, dataObj=self.scriptClass))
self.registerSection(TracebackScriptSection(self.handler, dataObj=self.scriptClass))
self.registerSection(PackageSection(self.handler))
-
+ self.registerSection(AddonSection(self.handler))
+
def preScriptPass(f):
# The first pass through kickstart file processing - look for %pre scripts
# and run them. This must come in a separate pass in case a script
@@ -1399,7 +1432,7 @@ def preScriptPass(f):
def parseKickstart(f):
# preprocessing the kickstart file has already been handled in initramfs.
- handler = AnacondaKSHandler()
+ handler = AnacondaKSHandler(constants.addon_paths)
ksparser = AnacondaKSParser(handler)
# We need this so all the /dev/disk/* stuff is set up before parsing.
diff --git a/pyanaconda/ui/__init__.py b/pyanaconda/ui/__init__.py
index 3f39a7a..9337a2a 100644
--- a/pyanaconda/ui/__init__.py
+++ b/pyanaconda/ui/__init__.py
@@ -63,6 +63,14 @@ class UserInterface(object):
basepath = os.path.dirname(__file__)
basemask = "pyanaconda.ui"
paths = PathDict({})
+
+ @classmethod
+ def update_paths(cls, pathdict):
+ """Receives pathdict and appends it's contents to the current
+ class defined search path dictionary."""
+ for k,v in pathdict.iteritems():
+ cls.paths.setdefault(k, [])
+ cls.paths[k].extend(v)
def setup(self, data):
"""Construct all the objects required to implement this interface.
--
1.7.11.7
More information about the anaconda-patches
mailing list