import os import lxml.etree import Bcfg2.Server.Lint from Bcfg2.Server import XI, XI_NAMESPACE class Duplicates(Bcfg2.Server.Lint.ServerPlugin): """ Find duplicate clients, groups, etc. """ def __init__(self, *args, **kwargs): Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs) self.groups_xdata = None self.clients_xdata = None self.load_xdata() def Run(self): """ run plugin """ # only run this plugin if we were not given a list of files. # not only is it marginally silly to run this plugin with a # partial list of files, it turns out to be really freaking # hard to get only a fragment of group or client metadata if self.groups_xdata is not None: self.duplicate_groups() self.duplicate_defaults() if self.clients_xdata is not None: self.duplicate_clients() @classmethod def Errors(cls): return {"broken-xinclude-chain":"warning", "duplicate-client":"error", "duplicate-group":"error", "duplicate-package":"error", "multiple-default-groups":"error"} def load_xdata(self): """ attempt to load XML data for groups and clients. only actually load data if all documents reference in XIncludes can be found in self.files""" if self.has_all_xincludes("groups.xml"): self.groups_xdata = self.metadata.clients_xml.xdata if self.has_all_xincludes("clients.xml"): self.clients_xdata = self.metadata.clients_xml.xdata def duplicate_groups(self): """ find duplicate groups """ self.duplicate_entries(self.clients_xdata.xpath('//Groups/Group'), 'group') def duplicate_clients(self): """ find duplicate clients """ self.duplicate_entries(self.clients_xdata.xpath('//Clients/Client'), 'client') def duplicate_entries(self, data, etype): """ generic duplicate entry finder """ seen = {} for el in data: if el.get('name') not in seen: seen[el.get('name')] = el else: self.LintError("duplicate-%s" % etype, "Duplicate %s '%s':\n%s\n%s" % (etype, el.get('name'), self.RenderXML(seen[el.get('name')]), self.RenderXML(el))) def duplicate_defaults(self): """ check for multiple default group definitions """ default_groups = [g for g in self.groups_xdata.findall('.//Group') if g.get('default') == 'true'] if len(default_groups) > 1: self.LintError("multiple-default-groups", "Multiple default groups defined: %s" % ",".join(default_groups)) def has_all_xincludes(self, mfile): """ return true if self.files includes all XIncludes listed in the specified metadata type, false otherwise""" if self.files is None: return True else: path = os.path.join(self.metadata.data, mfile) if path in self.files: xdata = lxml.etree.parse(path) for el in xdata.findall('./%sinclude' % XI_NAMESPACE): if not self.has_all_xincludes(el.get('href')): self.LintError("broken-xinclude-chain", "Broken XInclude chain: could not include %s" % path) return False return True