1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
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
|