""" ``bcfg2-lint`` plugin for :ref:`Metadata ` """ from Bcfg2.Server.Lint import ServerPlugin class Metadata(ServerPlugin): """ ``bcfg2-lint`` plugin for :ref:`Metadata `. This checks for several things: * ```` tags nested inside other ```` tags; * Deprecated options (like ``location="floating"``); * Profiles that don't exist, or that aren't profile groups; * Groups or clients that are defined multiple times; * Multiple default groups or a default group that isn't a profile group. """ def Run(self): self.nested_clients() self.deprecated_options() self.bogus_profiles() self.duplicate_groups() self.duplicate_default_groups() self.duplicate_clients() self.default_is_profile() @classmethod def Errors(cls): return {"nested-client-tags": "warning", "deprecated-clients-options": "warning", "nonexistent-profile-group": "error", "non-profile-set-as-profile": "error", "duplicate-group": "error", "duplicate-client": "error", "multiple-default-groups": "error", "default-is-not-profile": "error"} def deprecated_options(self): """ Check for the ``location='floating'`` option, which has been deprecated in favor of ``floating='true'``. """ if not hasattr(self.metadata, "clients_xml"): # using metadata database return clientdata = self.metadata.clients_xml.xdata for el in clientdata.xpath("//Client"): loc = el.get("location") if loc: if loc == "floating": floating = True else: floating = False self.LintError("deprecated-clients-options", "The location='%s' option is deprecated. " "Please use floating='%s' instead:\n%s" % (loc, floating, self.RenderXML(el))) def nested_clients(self): """ Check for a ```` tag inside a ```` tag, which is either redundant or will never match. """ groupdata = self.metadata.groups_xml.xdata for el in groupdata.xpath("//Client//Client"): self.LintError("nested-client-tags", "Client %s nested within Client tag: %s" % (el.get("name"), self.RenderXML(el))) def bogus_profiles(self): """ Check for clients that have profiles that are either not flagged as profile groups in ``groups.xml``, or don't exist. """ if not hasattr(self.metadata, "clients_xml"): # using metadata database return for client in self.metadata.clients_xml.xdata.findall('.//Client'): profile = client.get("profile") if profile not in self.metadata.groups: self.LintError("nonexistent-profile-group", "%s has nonexistent profile group %s:\n%s" % (client.get("name"), profile, self.RenderXML(client))) elif not self.metadata.groups[profile].is_profile: self.LintError("non-profile-set-as-profile", "%s is set as profile for %s, but %s is not a " "profile group:\n%s" % (profile, client.get("name"), profile, self.RenderXML(client))) def duplicate_default_groups(self): """ Check for multiple default groups. """ defaults = [] for grp in self.metadata.groups_xml.xdata.xpath("//Groups/Group") + \ self.metadata.groups_xml.xdata.xpath("//Groups/Group//Group"): if grp.get("default", "false").lower() == "true": defaults.append(self.RenderXML(grp)) if len(defaults) > 1: self.LintError("multiple-default-groups", "Multiple default groups defined:\n%s" % "\n".join(defaults)) def duplicate_clients(self): """ Check for clients that are defined more than once. """ if not hasattr(self.metadata, "clients_xml"): # using metadata database return self.duplicate_entries( self.metadata.clients_xml.xdata.xpath("//Client"), "client") def duplicate_groups(self): """ Check for groups that are defined more than once. We count a group tag as a definition if it a) has profile or public set; or b) has any children.""" allgroups = [ g for g in self.metadata.groups_xml.xdata.xpath("//Groups/Group") + self.metadata.groups_xml.xdata.xpath("//Groups/Group//Group") if g.get("profile") or g.get("public") or g.getchildren()] self.duplicate_entries(allgroups, "group") def duplicate_entries(self, allentries, etype): """ Generic duplicate entry finder. :param allentries: A list of all entries to check for duplicates. :type allentries: list of lxml.etree._Element :param etype: The entry type. This will be used to determine the error name (``duplicate-``) and for display to the end user. :type etype: string """ entries = dict() for el in allentries: if el.get("name") in entries: entries[el.get("name")].append(self.RenderXML(el)) else: entries[el.get("name")] = [self.RenderXML(el)] for ename, els in entries.items(): if len(els) > 1: self.LintError("duplicate-%s" % etype, "%s %s is defined multiple times:\n%s" % (etype.title(), ename, "\n".join(els))) def default_is_profile(self): """ Ensure that the default group is a profile group. """ if (self.metadata.default and not self.metadata.groups[self.metadata.default].is_profile): xdata = \ self.metadata.groups_xml.xdata.xpath("//Group[@name='%s']" % self.metadata.default)[0] self.LintError("default-is-not-profile", "Default group is not a profile group:\n%s" % self.RenderXML(xdata))