"""This provides bundle clauses with translation functionality."""
import copy
import logging
import lxml.etree
import os
import os.path
import re
import sys
import Bcfg2.Server
import Bcfg2.Server.Plugin
import Bcfg2.Server.Lint
try:
import genshi.template.base
from Bcfg2.Server.Plugins.TGenshi import removecomment, TemplateFile
HAS_GENSHI = True
except ImportError:
HAS_GENSHI = False
SETUP = None
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(TemplateFile,
Bcfg2.Server.Plugin.StructFile):
""" Representation of a Genshi-templated bundle XML file """
def __init__(self, name, specific, encoding, fam=None):
TemplateFile.__init__(self, name, specific, encoding)
Bcfg2.Server.Plugin.StructFile.__init__(self, name, fam=fam)
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(removecomment)
data = lxml.etree.XML(
stream.render('xml', strip_whitespace=False).encode(),
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
class Bundler(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.Structure,
Bcfg2.Server.Plugin.XMLDirectoryBacked):
""" The bundler creates dependent clauses based on the
bundle/translation scheme from Bcfg1. """
__author__ = 'bcfg-dev@mcs.anl.gov'
patterns = re.compile(r'^(?P<name>.*)\.(xml|genshi)$')
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
Bcfg2.Server.Plugin.XMLDirectoryBacked.__init__(self, self.data,
self.core.fam)
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,
fam=self.core.fam)
else:
raise Bcfg2.Server.Plugin.PluginExecutionError("Genshi not "
"available: %s"
% name)
else:
return BundleFile(name, fam=self.fam)
def BuildStructures(self, metadata):
"""Build all structures for client (metadata)."""
bundleset = []
bundle_entries = {}
for key, item in self.entries.items():
bundle_entries.setdefault(
self.patterns.match(os.path.basename(key)).group('name'),
[]).append(item)
for bundlename in metadata.bundles:
try:
entries = bundle_entries[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:
err = sys.exc_info()[1]
self.logger.error("Bundler: Failed to render templated bundle "
"%s: %s" % (bundlename, err))
except:
self.logger.error("Bundler: Unexpected bundler error for %s" %
bundlename, exc_info=1)
return bundleset
[docs]class BundlerLint(Bcfg2.Server.Lint.ServerPlugin):
""" Perform various :ref:`Bundler
<server-plugins-structures-bundler-index>` checks. """
def Run(self):
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))):
self.bundle_names(bundle)
@classmethod
def Errors(cls):
return {"bundle-not-found": "error",
"inconsistent-bundle-name": "warning"}
[docs] def missing_bundles(self):
""" Find bundles listed in Metadata but not implemented in
Bundler. """
if self.files is None:
# when given a list of files on stdin, this check is
# useless, so skip it
groupdata = self.metadata.groups_xml.xdata
ref_bundles = set([b.get("name")
for b in groupdata.findall("//Bundle")])
allbundles = self.core.plugins['Bundler'].entries.keys()
for bundle in ref_bundles:
xmlbundle = "%s.xml" % bundle
genshibundle = "%s.genshi" % bundle
if (xmlbundle not in allbundles and
genshibundle not in allbundles):
self.LintError("bundle-not-found",
"Bundle %s referenced, but does not exist" %
bundle)
[docs] def bundle_names(self, bundle):
""" Verify bundle name attribute matches filename.
:param bundle: The bundle to verify
:type bundle: Bcfg2.Server.Plugins.Bundler.BundleFile
"""
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))