summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Bundler.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/Bundler.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Bundler.py243
1 files changed, 108 insertions, 135 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py
index 7030c1574..d8290d844 100644
--- a/src/lib/Bcfg2/Server/Plugins/Bundler.py
+++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py
@@ -1,82 +1,41 @@
"""This provides bundle clauses with translation functionality."""
-import copy
-import logging
-import lxml.etree
import os
-import os.path
import re
import sys
+import copy
import Bcfg2.Server
import Bcfg2.Server.Plugin
import Bcfg2.Server.Lint
-
-try:
- import genshi.template.base
- import Bcfg2.Server.Plugins.TGenshi
- HAS_GENSHI = True
-except ImportError:
- HAS_GENSHI = False
-
-
-SETUP = None
+from genshi.template import TemplateError
class BundleFile(Bcfg2.Server.Plugin.StructFile):
""" Representation of a bundle XML file """
- def get_xml_value(self, metadata):
- """ get the XML data that applies to the given client """
- bundlename = os.path.splitext(os.path.basename(self.name))[0]
- bundle = lxml.etree.Element('Bundle', name=bundlename)
- for item in self.Match(metadata):
- bundle.append(copy.copy(item))
- return bundle
-
-
-if HAS_GENSHI:
- class BundleTemplateFile(Bcfg2.Server.Plugins.TGenshi.TemplateFile,
- Bcfg2.Server.Plugin.StructFile):
- """ Representation of a Genshi-templated bundle XML file """
-
- def __init__(self, name, specific, encoding):
- Bcfg2.Server.Plugins.TGenshi.TemplateFile.__init__(self, name,
- specific,
- encoding)
- Bcfg2.Server.Plugin.StructFile.__init__(self, name)
- self.logger = logging.getLogger(name)
-
- def get_xml_value(self, metadata):
- """ get the rendered XML data that applies to the given
- client """
- if not hasattr(self, 'template'):
- msg = "No parsed template information for %s" % self.name
- self.logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
- stream = self.template.generate(metadata=metadata,
- repo=SETUP['repo']).filter(
- Bcfg2.Server.Plugins.TGenshi.removecomment)
- data = lxml.etree.XML(stream.render('xml',
- strip_whitespace=False),
- parser=Bcfg2.Server.XMLParser)
- bundlename = os.path.splitext(os.path.basename(self.name))[0]
- bundle = lxml.etree.Element('Bundle', name=bundlename)
- for item in self.Match(metadata, data):
- bundle.append(copy.deepcopy(item))
- return bundle
-
- def Match(self, metadata, xdata): # pylint: disable=W0221
- """Return matching fragments of parsed template."""
- rv = []
- for child in xdata.getchildren():
- rv.extend(self._match(child, metadata))
- self.logger.debug("File %s got %d match(es)" % (self.name,
- len(rv)))
- return rv
-
- class SGenshiTemplateFile(BundleTemplateFile):
- """ provided for backwards compat with the deprecated SGenshi
- plugin """
- pass
+ bundle_name_re = re.compile('^(?P<name>.*)\.(xml|genshi)$')
+
+ def __init__(self, filename, should_monitor=False):
+ Bcfg2.Server.Plugin.StructFile.__init__(self, filename,
+ should_monitor=should_monitor)
+ if self.name.endswith(".genshi"):
+ self.logger.warning("Bundler: %s: Bundle filenames ending with "
+ ".genshi are deprecated; add the Genshi XML "
+ "namespace to a .xml bundle instead" %
+ self.name)
+ __init__.__doc__ = Bcfg2.Server.Plugin.StructFile.__init__.__doc__
+
+ def Index(self):
+ Bcfg2.Server.Plugin.StructFile.Index(self)
+ if self.xdata.get("name"):
+ self.logger.warning("Bundler: %s: Explicitly specifying bundle "
+ "names is deprecated" % self.name)
+ Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__
+
+ @property
+ def bundle_name(self):
+ """ The name of the bundle, as determined from the filename """
+ return self.bundle_name_re.match(
+ os.path.basename(self.name)).group("name")
class Bundler(Bcfg2.Server.Plugin.Plugin,
@@ -85,72 +44,85 @@ class Bundler(Bcfg2.Server.Plugin.Plugin,
""" The bundler creates dependent clauses based on the
bundle/translation scheme from Bcfg1. """
__author__ = 'bcfg-dev@mcs.anl.gov'
- patterns = re.compile('^(?P<name>.*)\.(xml|genshi)$')
+ __child__ = BundleFile
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
Bcfg2.Server.Plugin.Structure.__init__(self)
- self.encoding = core.setup['encoding']
- self.__child__ = self.template_dispatch
- try:
- Bcfg2.Server.Plugin.XMLDirectoryBacked.__init__(self,
- self.data,
- self.core.fam)
- except OSError:
- err = sys.exc_info()[1]
- msg = "Failed to load Bundle repository %s: %s" % (self.data, err)
- self.logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginInitError(msg)
-
- global SETUP
- SETUP = core.setup
-
- def template_dispatch(self, name, _):
- """ Add the correct child entry type to Bundler depending on
- whether the XML file in question is a plain XML file or a
- templated bundle """
- bundle = lxml.etree.parse(name, parser=Bcfg2.Server.XMLParser)
- nsmap = bundle.getroot().nsmap
- if (name.endswith('.genshi') or
- ('py' in nsmap and
- nsmap['py'] == 'http://genshi.edgewall.org/')):
- if HAS_GENSHI:
- spec = Bcfg2.Server.Plugin.Specificity()
- return BundleTemplateFile(name, spec, self.encoding)
- else:
- raise Bcfg2.Server.Plugin.PluginExecutionError("Genshi not "
- "available: %s"
- % name)
- else:
- return BundleFile(name, self.fam)
+ Bcfg2.Server.Plugin.XMLDirectoryBacked.__init__(self, self.data)
+ #: Bundles by bundle name, rather than filename
+ self.bundles = dict()
+ __init__.__doc__ = Bcfg2.Server.Plugin.Plugin.__init__.__doc__
- def BuildStructures(self, metadata):
- """Build all structures for client (metadata)."""
- bundleset = []
+ def HandleEvent(self, event):
+ Bcfg2.Server.Plugin.XMLDirectoryBacked.HandleEvent(self, event)
- bundle_entries = {}
- for key, item in self.entries.items():
- bundle_entries.setdefault(
- self.patterns.match(os.path.basename(key)).group('name'),
- []).append(item)
+ self.bundles = dict([(b.bundle_name, b)
+ for b in self.entries.values()])
+ HandleEvent.__doc__ = \
+ Bcfg2.Server.Plugin.XMLDirectoryBacked.HandleEvent.__doc__
- for bundlename in metadata.bundles:
+ def BuildStructures(self, metadata):
+ bundleset = []
+ bundles = copy.copy(metadata.bundles)
+ bundles_added = set(bundles)
+ while bundles:
+ bundlename = bundles.pop()
try:
- entries = bundle_entries[bundlename]
+ bundle = self.bundles[bundlename]
except KeyError:
self.logger.error("Bundler: Bundle %s does not exist" %
bundlename)
continue
+
try:
- bundleset.append(entries[0].get_xml_value(metadata))
- except genshi.template.base.TemplateError:
+ data = bundle.XMLMatch(metadata)
+ except TemplateError:
err = sys.exc_info()[1]
self.logger.error("Bundler: Failed to render templated bundle "
"%s: %s" % (bundlename, err))
+ continue
except:
self.logger.error("Bundler: Unexpected bundler error for %s" %
bundlename, exc_info=1)
+ continue
+
+ if data.get("independent", "false").lower() == "true":
+ data.tag = "Independent"
+ del data.attrib['independent']
+
+ data.set("name", bundlename)
+
+ for child in data.findall("Bundle"):
+ if child.getchildren():
+ # XInclude'd bundle -- "flatten" it so there
+ # aren't extra Bundle tags, since other bits in
+ # Bcfg2 only handle the direct children of the
+ # top-level Bundle tag
+ if data.get("name"):
+ self.logger.warning("Bundler: In file XIncluded from "
+ "%s: Explicitly specifying "
+ "bundle names is deprecated" %
+ self.name)
+ for el in child.getchildren():
+ data.append(el)
+ data.remove(child)
+ elif child.get("name"):
+ # dependent bundle -- add it to the list of
+ # bundles for this client
+ if child.get("name") not in bundles_added:
+ bundles.append(child.get("name"))
+ bundles_added.add(child.get("name"))
+ data.remove(child)
+ else:
+ # neither name or children -- wat
+ self.logger.warning("Bundler: Useless empty Bundle tag "
+ "in %s" % self.name)
+ data.remove(child)
+ bundleset.append(data)
return bundleset
+ BuildStructures.__doc__ = \
+ Bcfg2.Server.Plugin.Structure.BuildStructures.__doc__
class BundlerLint(Bcfg2.Server.Lint.ServerPlugin):
@@ -160,15 +132,15 @@ class BundlerLint(Bcfg2.Server.Lint.ServerPlugin):
""" run plugin """
self.missing_bundles()
for bundle in self.core.plugins['Bundler'].entries.values():
- if (self.HandlesFile(bundle.name) and
- (not HAS_GENSHI or
- not isinstance(bundle, BundleTemplateFile))):
+ if self.HandlesFile(bundle.name):
self.bundle_names(bundle)
@classmethod
def Errors(cls):
return {"bundle-not-found": "error",
- "inconsistent-bundle-name": "warning"}
+ "unused-bundle": "warning",
+ "explicit-bundle-name": "error",
+ "genshi-extension-bundle": "error"}
def missing_bundles(self):
""" find bundles listed in Metadata but not implemented in Bundler """
@@ -179,27 +151,28 @@ class BundlerLint(Bcfg2.Server.Lint.ServerPlugin):
ref_bundles = set([b.get("name")
for b in groupdata.findall("//Bundle")])
- allbundles = self.core.plugins['Bundler'].entries.keys()
+ allbundles = self.core.plugins['Bundler'].bundles.keys()
for bundle in ref_bundles:
- xmlbundle = "%s.xml" % bundle
- genshibundle = "%s.genshi" % bundle
- if (xmlbundle not in allbundles and
- genshibundle not in allbundles):
+ if bundle not in allbundles:
self.LintError("bundle-not-found",
"Bundle %s referenced, but does not exist" %
bundle)
+ for bundle in allbundles:
+ if bundle not in ref_bundles:
+ self.LintError("unused-bundle",
+ "Bundle %s defined, but is not referenced "
+ "in Metadata" % bundle)
+
def bundle_names(self, bundle):
- """ verify bundle name attribute matches filename """
- try:
- xdata = lxml.etree.XML(bundle.data)
- except AttributeError:
- # genshi template
- xdata = lxml.etree.parse(bundle.template.filepath).getroot()
-
- fname = os.path.splitext(os.path.basename(bundle.name))[0]
- bname = xdata.get('name')
- if fname != bname:
- self.LintError("inconsistent-bundle-name",
- "Inconsistent bundle name: filename is %s, "
- "bundle name is %s" % (fname, bname))
+ """ Verify that deprecated bundle .genshi bundles and explicit
+ bundle names aren't used """
+ if bundle.xdata.get('name'):
+ self.LintError("explicit-bundle-name",
+ "Deprecated explicit bundle name in %s" %
+ bundle.name)
+
+ if bundle.name.endswith(".genshi"):
+ self.LintError("genshi-extension-bundle",
+ "Bundle %s uses deprecated .genshi extension" %
+ bundle.name)