""" ``bcfg2-lint`` plugin for :ref:`Metadata
<server-plugins-grouping-metadata>` """
from Bcfg2.Server.Lint import ServerPlugin
[docs]class Metadata(ServerPlugin):
""" ``bcfg2-lint`` plugin for :ref:`Metadata
<server-plugins-grouping-metadata>`. This checks for several things:
* ``<Client>`` tags nested inside other ``<Client>`` 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"}
[docs] 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)))
[docs] def nested_clients(self):
""" Check for a ``<Client/>`` tag inside a ``<Client/>`` 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)))
[docs] 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)))
[docs] 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))
[docs] 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")
[docs] def duplicate_groups(self):
""" Check for groups that are defined more than once. There
are two ways this can happen:
1. The group is listed twice with contradictory options.
2. The group is listed with no options *first*, and then with
options later.
In this context, 'first' refers to the order in which groups
are parsed; see the loop condition below and
_handle_groups_xml_event above for details. """
groups = dict()
duplicates = dict()
for grp in self.metadata.groups_xml.xdata.xpath("//Groups/Group") + \
self.metadata.groups_xml.xdata.xpath("//Groups/Group//Group"):
grpname = grp.get("name")
if grpname in duplicates:
duplicates[grpname].append(grp)
elif len(grp.attrib) > 1: # group has options
if grpname in groups:
duplicates[grpname] = [grp, groups[grpname]]
else:
groups[grpname] = grp
else: # group has no options
groups[grpname] = grp
for grpname, grps in duplicates.items():
self.LintError("duplicate-group",
"Group %s is defined multiple times:\n%s" %
(grpname,
"\n".join(self.RenderXML(g) for g in grps)))
[docs] 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-<etype>``) 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)))
[docs] 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))