summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Lint
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Server/Lint')
-rw-r--r--src/lib/Server/Lint/Bundles.py61
-rw-r--r--src/lib/Server/Lint/Comments.py187
-rw-r--r--src/lib/Server/Lint/Deltas.py19
-rw-r--r--src/lib/Server/Lint/Duplicates.py81
-rwxr-xr-xsrc/lib/Server/Lint/Genshi.py28
-rw-r--r--src/lib/Server/Lint/GroupPatterns.py31
-rw-r--r--src/lib/Server/Lint/InfoXML.py42
-rw-r--r--src/lib/Server/Lint/MergeFiles.py69
-rw-r--r--src/lib/Server/Lint/Pkgmgr.py35
-rw-r--r--src/lib/Server/Lint/RequiredAttrs.py138
-rw-r--r--src/lib/Server/Lint/Validate.py200
-rw-r--r--src/lib/Server/Lint/__init__.py218
12 files changed, 12 insertions, 1097 deletions
diff --git a/src/lib/Server/Lint/Bundles.py b/src/lib/Server/Lint/Bundles.py
deleted file mode 100644
index 472915cfd..000000000
--- a/src/lib/Server/Lint/Bundles.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import lxml.etree
-import Bcfg2.Server.Lint
-
-class Bundles(Bcfg2.Server.Lint.ServerPlugin):
- """ Perform various bundle checks """
-
- def Run(self):
- """ run plugin """
- if 'Bundler' in self.core.plugins:
- self.missing_bundles()
- for bundle in self.core.plugins['Bundler'].entries.values():
- if self.HandlesFile(bundle.name):
- if (not Bcfg2.Server.Plugins.Bundler.have_genshi or
- type(bundle) is not
- Bcfg2.Server.Plugins.SGenshi.SGenshiTemplateFile):
- self.bundle_names(bundle)
-
- 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)
-
- def bundle_names(self, bundle):
- """ verify bundle name attribute matches filename """
- try:
- xdata = lxml.etree.XML(bundle.data)
- except AttributeError:
- # genshi template
- xdata = lxml.etree.parse(bundle.template.filepath).getroot()
-
- fname = bundle.name.split('Bundler/')[1].split('.')[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))
-
- def sgenshi_groups(self, bundle):
- """ ensure that Genshi Bundles do not include <Group> tags,
- which are not supported """
- xdata = lxml.etree.parse(bundle.name)
- groups = [self.RenderXML(g)
- for g in xdata.getroottree().findall("//Group")]
- if groups:
- self.LintError("group-tag-not-allowed",
- "<Group> tag is not allowed in SGenshi Bundle:\n%s" %
- "\n".join(groups))
diff --git a/src/lib/Server/Lint/Comments.py b/src/lib/Server/Lint/Comments.py
deleted file mode 100644
index 19fae1b08..000000000
--- a/src/lib/Server/Lint/Comments.py
+++ /dev/null
@@ -1,187 +0,0 @@
-import os.path
-import lxml.etree
-import Bcfg2.Server.Lint
-
-class Comments(Bcfg2.Server.Lint.ServerPlugin):
- """ check files for various required headers """
- def __init__(self, *args, **kwargs):
- Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs)
- self.config_cache = {}
-
- def Run(self):
- self.check_bundles()
- self.check_properties()
- self.check_metadata()
- self.check_cfg()
- self.check_infoxml()
- self.check_probes()
-
- def required_keywords(self, rtype):
- """ given a file type, fetch the list of required VCS keywords
- from the bcfg2-lint config """
- return self.required_items(rtype, "keyword")
-
- def required_comments(self, rtype):
- """ given a file type, fetch the list of required comments
- from the bcfg2-lint config """
- return self.required_items(rtype, "comment")
-
- def required_items(self, rtype, itype):
- """ given a file type and item type (comment or keyword),
- fetch the list of required items from the bcfg2-lint config """
- if itype not in self.config_cache:
- self.config_cache[itype] = {}
-
- if rtype not in self.config_cache[itype]:
- rv = []
- global_item = "global_%ss" % itype
- if global_item in self.config:
- rv.extend(self.config[global_item].split(","))
-
- item = "%s_%ss" % (rtype.lower(), itype)
- if item in self.config:
- if self.config[item]:
- rv.extend(self.config[item].split(","))
- else:
- # config explicitly specifies nothing
- rv = []
- self.config_cache[itype][rtype] = rv
- return self.config_cache[itype][rtype]
-
- def check_bundles(self):
- """ check bundle files for required headers """
- if 'Bundler' in self.core.plugins:
- for bundle in self.core.plugins['Bundler'].entries.values():
- xdata = None
- rtype = ""
- try:
- xdata = lxml.etree.XML(bundle.data)
- rtype = "bundler"
- except (lxml.etree.XMLSyntaxError, AttributeError):
- xdata = lxml.etree.parse(bundle.template.filepath).getroot()
- rtype = "sgenshi"
-
- self.check_xml(bundle.name, xdata, rtype)
-
- def check_properties(self):
- """ check properties files for required headers """
- if 'Properties' in self.core.plugins:
- props = self.core.plugins['Properties']
- for propfile, pdata in props.store.entries.items():
- if os.path.splitext(propfile)[1] == ".xml":
- self.check_xml(pdata.name, pdata.xdata, 'properties')
-
- def check_metadata(self):
- """ check metadata files for required headers """
- if self.has_all_xincludes("groups.xml"):
- self.check_xml(os.path.join(self.metadata.data, "groups.xml"),
- self.metadata.groups_xml.data,
- "metadata")
- if self.has_all_xincludes("clients.xml"):
- self.check_xml(os.path.join(self.metadata.data, "clients.xml"),
- self.metadata.clients_xml.data,
- "metadata")
-
- def check_cfg(self):
- """ check Cfg files for required headers """
- if 'Cfg' in self.core.plugins:
- for entryset in self.core.plugins['Cfg'].entries.values():
- for entry in entryset.entries.values():
- if entry.name.endswith(".genshi"):
- rtype = "tgenshi"
- else:
- rtype = "cfg"
- self.check_plaintext(entry.name, entry.data, rtype)
-
- def check_infoxml(self):
- """ check info.xml files for required headers """
- if 'Cfg' in self.core.plugins:
- for entryset in self.core.plugins['Cfg'].entries.items():
- if (hasattr(entryset, "infoxml") and
- entryset.infoxml is not None):
- self.check_xml(entryset.infoxml.name,
- entryset.infoxml.pnode.data,
- "infoxml")
-
- def check_probes(self):
- """ check probes for required headers """
- if 'Probes' in self.core.plugins:
- for probe in self.core.plugins['Probes'].probes.entries.values():
- self.check_plaintext(probe.name, probe.data, "probes")
-
- def check_xml(self, filename, xdata, rtype):
- """ check generic XML files for required headers """
- self.check_lines(filename,
- [str(el)
- for el in xdata.getiterator(lxml.etree.Comment)],
- rtype)
-
- def check_plaintext(self, filename, data, rtype):
- """ check generic plaintex files for required headers """
- self.check_lines(filename, data.splitlines(), rtype)
-
- def check_lines(self, filename, lines, rtype):
- """ generic header check for a set of lines """
- if self.HandlesFile(filename):
- # found is trivalent:
- # False == not found
- # None == found but not expanded
- # True == found and expanded
- found = dict((k, False) for k in self.required_keywords(rtype))
-
- for line in lines:
- # we check for both '$<keyword>:' and '$<keyword>$' to see
- # if the keyword just hasn't been expanded
- for (keyword, status) in found.items():
- if not status:
- if '$%s:' % keyword in line:
- found[keyword] = True
- elif '$%s$' % keyword in line:
- found[keyword] = None
-
- unexpanded = [keyword for (keyword, status) in found.items()
- if status is None]
- if unexpanded:
- self.LintError("unexpanded-keywords",
- "%s: Required keywords(s) found but not expanded: %s" %
- (filename, ", ".join(unexpanded)))
- missing = [keyword for (keyword, status) in found.items()
- if status is False]
- if missing:
- self.LintError("keywords-not-found",
- "%s: Required keywords(s) not found: $%s$" %
- (filename, "$, $".join(missing)))
-
- # next, check for required comments. found is just
- # boolean
- found = dict((k, False) for k in self.required_comments(rtype))
-
- for line in lines:
- for (comment, status) in found.items():
- if not status:
- found[comment] = comment in line
-
- missing = [comment for (comment, status) in found.items()
- if status is False]
- if missing:
- self.LintError("comments-not-found",
- "%s: Required comments(s) not found: %s" %
- (filename, ", ".join(missing)))
-
- 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('./{http://www.w3.org/2001/XInclude}include'):
- 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
-
diff --git a/src/lib/Server/Lint/Deltas.py b/src/lib/Server/Lint/Deltas.py
index cf91d1d13..114f2e348 100644
--- a/src/lib/Server/Lint/Deltas.py
+++ b/src/lib/Server/Lint/Deltas.py
@@ -1,4 +1,5 @@
import Bcfg2.Server.Lint
+from Bcfg2.Server.Plugins.Cfg import CfgFilter
class Deltas(Bcfg2.Server.Lint.ServerPlugin):
""" Warn about usage of .cat and .diff files """
@@ -10,11 +11,15 @@ class Deltas(Bcfg2.Server.Lint.ServerPlugin):
for basename, entry in list(cfg.entries.items()):
self.check_entry(basename, entry)
+ @classmethod
+ def Errors(cls):
+ return {"cat-file-used":"warning",
+ "diff-file-used":"warning"}
+
def check_entry(self, basename, entry):
- for fname in list(entry.entries.keys()):
- if self.HandlesFile(fname):
- match = entry.specific.delta_reg.match(fname)
- if match:
- self.LintError("%s-file-used" % match.group('delta'),
- "%s file used on %s: %s" %
- (match.group('delta'), basename, fname))
+ for fname, processor in entry.entries.items():
+ if self.HandlesFile(fname) and isinstance(processor, CfgFilter):
+ extension = fname.split(".")[-1]
+ self.LintError("%s-file-used" % extension,
+ "%s file used on %s: %s" %
+ (extension, basename, fname))
diff --git a/src/lib/Server/Lint/Duplicates.py b/src/lib/Server/Lint/Duplicates.py
deleted file mode 100644
index 75f620603..000000000
--- a/src/lib/Server/Lint/Duplicates.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import os.path
-import lxml.etree
-import Bcfg2.Server.Lint
-
-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()
-
- 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('./{http://www.w3.org/2001/XInclude}include'):
- 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
diff --git a/src/lib/Server/Lint/Genshi.py b/src/lib/Server/Lint/Genshi.py
deleted file mode 100755
index 56803246c..000000000
--- a/src/lib/Server/Lint/Genshi.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import genshi.template
-import Bcfg2.Server.Lint
-
-class Genshi(Bcfg2.Server.Lint.ServerPlugin):
- """ Check Genshi templates for syntax errors """
-
- def Run(self):
- """ run plugin """
- loader = genshi.template.TemplateLoader()
- for plugin in ['Cfg', 'TGenshi']:
- if plugin in self.core.plugins:
- self.check_files(self.core.plugins[plugin].entries,
- loader=loader)
-
- def check_files(self, entries, loader=None):
- if loader is None:
- loader = genshi.template.TemplateLoader()
-
- for eset in entries.values():
- for fname, sdata in list(eset.entries.items()):
- if (self.HandlesFile(fname) and
- (fname.endswith(".genshi") or fname.endswith(".newtxt"))):
- try:
- loader.load(sdata.name,
- cls=genshi.template.NewTextTemplate)
- except genshi.template.TemplateSyntaxError, err:
- self.LintError("genshi-syntax-error",
- "Genshi syntax error: %s" % err)
diff --git a/src/lib/Server/Lint/GroupPatterns.py b/src/lib/Server/Lint/GroupPatterns.py
deleted file mode 100644
index b69d7a5d8..000000000
--- a/src/lib/Server/Lint/GroupPatterns.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import sys
-import Bcfg2.Server.Lint
-from Bcfg2.Server.Plugins.GroupPatterns import PatternMap
-
-class GroupPatterns(Bcfg2.Server.Lint.ServerPlugin):
- """ Check Genshi templates for syntax errors """
-
- def Run(self):
- """ run plugin """
- if 'GroupPatterns' in self.core.plugins:
- cfg = self.core.plugins['GroupPatterns'].config
- for entry in cfg.xdata.xpath('//GroupPattern'):
- groups = [g.text for g in entry.findall('Group')]
- self.check(entry, groups, ptype='NamePattern')
- self.check(entry, groups, ptype='NameRange')
-
- def check(self, entry, groups, ptype="NamePattern"):
- if ptype == "NamePattern":
- pmap = lambda p: PatternMap(p, None, groups)
- else:
- pmap = lambda p: PatternMap(None, p, groups)
-
- for el in entry.findall(ptype):
- pat = el.text
- try:
- pmap(pat)
- except:
- err = sys.exc_info()[1]
- self.LintError("pattern-fails-to-initialize",
- "Failed to initialize %s %s for %s: %s" %
- (ptype, pat, entry.get('pattern'), err))
diff --git a/src/lib/Server/Lint/InfoXML.py b/src/lib/Server/Lint/InfoXML.py
deleted file mode 100644
index 2054e23bf..000000000
--- a/src/lib/Server/Lint/InfoXML.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import os.path
-import Bcfg2.Options
-import Bcfg2.Server.Lint
-
-class InfoXML(Bcfg2.Server.Lint.ServerPlugin):
- """ ensure that all config files have an info.xml file"""
-
- def Run(self):
- if 'Cfg' in self.core.plugins:
- for filename, entryset in self.core.plugins['Cfg'].entries.items():
- infoxml_fname = os.path.join(entryset.path, "info.xml")
- if self.HandlesFile(infoxml_fname):
- if (hasattr(entryset, "infoxml") and
- entryset.infoxml is not None):
- self.check_infoxml(infoxml_fname,
- entryset.infoxml.pnode.data)
- else:
- self.LintError("no-infoxml",
- "No info.xml found for %s" % filename)
-
- def check_infoxml(self, fname, xdata):
- for info in xdata.getroottree().findall("//Info"):
- required = []
- if "required_attrs" in self.config:
- required = self.config["required_attrs"].split(",")
-
- missing = [attr for attr in required if info.get(attr) is None]
- if missing:
- self.LintError("required-infoxml-attrs-missing",
- "Required attribute(s) %s not found in %s:%s" %
- (",".join(missing), fname, self.RenderXML(info)))
-
- if ((Bcfg2.Options.MDATA_PARANOID.value and
- info.get("paranoid") is not None and
- info.get("paranoid").lower() == "false") or
- (not Bcfg2.Options.MDATA_PARANOID.value and
- (info.get("paranoid") is None or
- info.get("paranoid").lower() != "true"))):
- self.LintError("paranoid-false",
- "Paranoid must be true in %s:%s" %
- (fname, self.RenderXML(info)))
-
diff --git a/src/lib/Server/Lint/MergeFiles.py b/src/lib/Server/Lint/MergeFiles.py
deleted file mode 100644
index ff6e3449a..000000000
--- a/src/lib/Server/Lint/MergeFiles.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import os
-import copy
-from difflib import SequenceMatcher
-import Bcfg2.Server.Lint
-
-class MergeFiles(Bcfg2.Server.Lint.ServerPlugin):
- """ find Probes or Cfg files with multiple similar files that
- might be merged into one """
-
- def Run(self):
- if 'Cfg' in self.core.plugins:
- self.check_cfg()
- if 'Probes' in self.core.plugins:
- self.check_probes()
-
- def check_cfg(self):
- for filename, entryset in self.core.plugins['Cfg'].entries.items():
- for mset in self.get_similar(entryset.entries):
- self.LintError("merge-cfg",
- "The following files are similar: %s. "
- "Consider merging them into a single Genshi "
- "template." %
- ", ".join([os.path.join(filename, p)
- for p in mset]))
-
- def check_probes(self):
- probes = self.core.plugins['Probes'].probes.entries
- for mset in self.get_similar(probes):
- self.LintError("merge-cfg",
- "The following probes are similar: %s. "
- "Consider merging them into a single probe." %
- ", ".join([p for p in mset]))
-
- def get_similar(self, entries):
- if "threshold" in self.config:
- # accept threshold either as a percent (e.g., "threshold=75") or
- # as a ratio (e.g., "threshold=.75")
- threshold = float(self.config['threshold'])
- if threshold > 1:
- threshold /= 100
- else:
- threshold = 0.75
- rv = []
- elist = entries.items()
- while elist:
- result = self._find_similar(elist.pop(0), copy.copy(elist),
- threshold)
- if len(result) > 1:
- elist = [(fname, fdata)
- for fname, fdata in elist
- if fname not in result]
- rv.append(result)
- return rv
-
- def _find_similar(self, ftuple, others, threshold):
- fname, fdata = ftuple
- rv = [fname]
- while others:
- cname, cdata = others.pop(0)
- sm = SequenceMatcher(None, fdata.data, cdata.data)
- # perform progressively more expensive comparisons
- if (sm.real_quick_ratio() > threshold and
- sm.quick_ratio() > threshold and
- sm.ratio() > threshold):
- rv.extend(self._find_similar((cname, cdata), copy.copy(others),
- threshold))
- return rv
-
-
diff --git a/src/lib/Server/Lint/Pkgmgr.py b/src/lib/Server/Lint/Pkgmgr.py
deleted file mode 100644
index 8f099163a..000000000
--- a/src/lib/Server/Lint/Pkgmgr.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import glob
-import lxml.etree
-import Bcfg2.Server.Lint
-
-class Pkgmgr(Bcfg2.Server.Lint.ServerlessPlugin):
- """ find duplicate Pkgmgr entries with the same priority """
-
- def Run(self):
- pset = set()
- for pfile in glob.glob("%s/Pkgmgr/*.xml" % self.config['repo']):
- if self.HandlesFile(pfile):
- xdata = lxml.etree.parse(pfile).getroot()
- # get priority, type, group
- priority = xdata.get('priority')
- ptype = xdata.get('type')
- for pkg in xdata.xpath("//Package"):
- if pkg.getparent().tag == 'Group':
- grp = pkg.getparent().get('name')
- if (type(grp) is not str and
- grp.getparent().tag == 'Group'):
- pgrp = grp.getparent().get('name')
- else:
- pgrp = 'none'
- else:
- grp = 'none'
- pgrp = 'none'
- ptuple = (pkg.get('name'), priority, ptype, grp, pgrp)
- # check if package is already listed with same
- # priority, type, grp
- if ptuple in pset:
- self.LintError("duplicate-package",
- "Duplicate Package %s, priority:%s, type:%s" %
- (pkg.get('name'), priority, ptype))
- else:
- pset.add(ptuple)
diff --git a/src/lib/Server/Lint/RequiredAttrs.py b/src/lib/Server/Lint/RequiredAttrs.py
deleted file mode 100644
index 55206d2ba..000000000
--- a/src/lib/Server/Lint/RequiredAttrs.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import os.path
-import lxml.etree
-import Bcfg2.Server.Lint
-from Bcfg2.Server.Plugins.Packages import Apt, Yum
-
-class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin):
- """ verify attributes for configuration entries (as defined in
- doc/server/configurationentries) """
-
- def __init__(self, *args, **kwargs):
- Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs)
- self.required_attrs = {
- 'Path': {
- 'device': ['name', 'owner', 'group', 'dev_type'],
- 'directory': ['name', 'owner', 'group', 'perms'],
- 'file': ['name', 'owner', 'group', 'perms', '__text__'],
- 'hardlink': ['name', 'to'],
- 'symlink': ['name', 'to'],
- 'ignore': ['name'],
- 'nonexistent': ['name'],
- 'permissions': ['name', 'owner', 'group', 'perms'],
- 'vcs': ['vcstype', 'revision', 'sourceurl']},
- 'Service': {
- 'chkconfig': ['name'],
- 'deb': ['name'],
- 'rc-update': ['name'],
- 'smf': ['name', 'FMRI'],
- 'upstart': ['name']},
- 'Action': ['name', 'timing', 'when', 'status', 'command'],
- 'Package': ['name']}
-
- def Run(self):
- self.check_packages()
- if "Defaults" in self.core.plugins:
- self.logger.info("Defaults plugin enabled; skipping required "
- "attribute checks")
- else:
- self.check_rules()
- self.check_bundles()
-
- def check_packages(self):
- """ check package sources for Source entries with missing attrs """
- if 'Packages' in self.core.plugins:
- for source in self.core.plugins['Packages'].sources:
- if isinstance(source, Yum.YumSource):
- if (not source.pulp_id and not source.url and
- not source.rawurl):
- self.LintError("required-attrs-missing",
- "A %s source must have either a url, "
- "rawurl, or pulp_id attribute: %s" %
- (source.ptype,
- self.RenderXML(source.xsource)))
- elif not source.url and not source.rawurl:
- self.LintError("required-attrs-missing",
- "A %s source must have either a url or "
- "rawurl attribute: %s" %
- (source.ptype,
- self.RenderXML(source.xsource)))
-
- if (not isinstance(source, Apt.AptSource) and
- source.recommended):
- self.LintError("extra-attrs",
- "The recommended attribute is not "
- "supported on %s sources: %s" %
- (source.ptype,
- self.RenderXML(source.xsource)))
-
- def check_rules(self):
- """ check Rules for Path entries with missing attrs """
- if 'Rules' in self.core.plugins:
- for rules in self.core.plugins['Rules'].entries.values():
- xdata = rules.pnode.data
- for path in xdata.xpath("//Path"):
- self.check_entry(path, os.path.join(self.config['repo'],
- rules.name))
-
- def check_bundles(self):
- """ check bundles for BoundPath entries with missing attrs """
- if 'Bundler' in self.core.plugins:
- for bundle in self.core.plugins['Bundler'].entries.values():
- try:
- xdata = lxml.etree.XML(bundle.data)
- except (lxml.etree.XMLSyntaxError, AttributeError):
- xdata = lxml.etree.parse(bundle.template.filepath).getroot()
-
- for path in xdata.xpath("//*[substring(name(), 1, 5) = 'Bound']"):
- self.check_entry(path, bundle.name)
-
- def check_entry(self, entry, filename):
- """ generic entry check """
- if self.HandlesFile(filename):
- name = entry.get('name')
- tag = entry.tag
- if tag.startswith("Bound"):
- tag = tag[5:]
- if tag not in self.required_attrs:
- self.LintError("unknown-entry-tag",
- "Unknown entry tag '%s': %s" %
- (entry.tag, self.RenderXML(entry)))
-
- if isinstance(self.required_attrs[tag], dict):
- etype = entry.get('type')
- if etype in self.required_attrs[tag]:
- required_attrs = set(self.required_attrs[tag][etype] +
- ['type'])
- else:
- self.LintError("unknown-entry-type",
- "Unknown %s type %s: %s" %
- (tag, etype, self.RenderXML(entry)))
- return
- else:
- required_attrs = set(self.required_attrs[tag])
- attrs = set(entry.attrib.keys())
-
- if 'dev_type' in required_attrs:
- dev_type = entry.get('dev_type')
- if dev_type in ['block', 'char']:
- # check if major/minor are specified
- required_attrs |= set(['major', 'minor'])
-
- if '__text__' in required_attrs:
- required_attrs.remove('__text__')
- if (not entry.text and
- not entry.get('empty', 'false').lower() == 'true'):
- self.LintError("required-attrs-missing",
- "Text missing for %s %s in %s: %s" %
- (entry.tag, name, filename,
- self.RenderXML(entry)))
-
- if not attrs.issuperset(required_attrs):
- self.LintError("required-attrs-missing",
- "The following required attribute(s) are "
- "missing for %s %s in %s: %s\n%s" %
- (entry.tag, name, filename,
- ", ".join([attr
- for attr in
- required_attrs.difference(attrs)]),
- self.RenderXML(entry)))
diff --git a/src/lib/Server/Lint/Validate.py b/src/lib/Server/Lint/Validate.py
deleted file mode 100644
index 952a65365..000000000
--- a/src/lib/Server/Lint/Validate.py
+++ /dev/null
@@ -1,200 +0,0 @@
-import fnmatch
-import glob
-import lxml.etree
-import os
-from subprocess import Popen, PIPE, STDOUT
-import sys
-
-import Bcfg2.Server.Lint
-
-class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
- """ Ensure that the repo validates """
-
- def __init__(self, *args, **kwargs):
- Bcfg2.Server.Lint.ServerlessPlugin.__init__(self, *args, **kwargs)
- self.filesets = {"metadata:groups":"%s/metadata.xsd",
- "metadata:clients":"%s/clients.xsd",
- "info":"%s/info.xsd",
- "%s/Bundler/*.xml":"%s/bundle.xsd",
- "%s/Bundler/*.genshi":"%s/bundle.xsd",
- "%s/Pkgmgr/*.xml":"%s/pkglist.xsd",
- "%s/Base/*.xml":"%s/base.xsd",
- "%s/Rules/*.xml":"%s/rules.xsd",
- "%s/Defaults/*.xml":"%s/defaults.xsd",
- "%s/etc/report-configuration.xml":"%s/report-configuration.xsd",
- "%s/Svcmgr/*.xml":"%s/services.xsd",
- "%s/Deps/*.xml":"%s/deps.xsd",
- "%s/Decisions/*.xml":"%s/decisions.xsd",
- "%s/Packages/sources.xml":"%s/packages.xsd",
- "%s/GroupPatterns/config.xml":"%s/grouppatterns.xsd",
- "%s/NagiosGen/config.xml":"%s/nagiosgen.xsd",
- "%s/FileProbes/config.xml":"%s/fileprobes.xsd",
- }
-
- self.filelists = {}
- self.get_filelists()
-
- def Run(self):
- schemadir = self.config['schema']
-
- for path, schemaname in self.filesets.items():
- try:
- filelist = self.filelists[path]
- except KeyError:
- filelist = []
-
- if filelist:
- # avoid loading schemas for empty file lists
- schemafile = schemaname % schemadir
- try:
- schema = lxml.etree.XMLSchema(lxml.etree.parse(schemafile))
- except IOError:
- e = sys.exc_info()[1]
- self.LintError("input-output-error", str(e))
- continue
- except lxml.etree.XMLSchemaParseError:
- e = sys.exc_info()[1]
- self.LintError("schema-failed-to-parse",
- "Failed to process schema %s: %s" %
- (schemafile, e))
- continue
- for filename in filelist:
- self.validate(filename, schemafile, schema=schema)
-
- self.check_properties()
-
- def check_properties(self):
- """ check Properties files against their schemas """
- for filename in self.filelists['props']:
- schemafile = "%s.xsd" % os.path.splitext(filename)[0]
- if os.path.exists(schemafile):
- self.validate(filename, schemafile)
- else:
- self.LintError("properties-schema-not-found",
- "No schema found for %s" % filename)
-
- def validate(self, filename, schemafile, schema=None):
- """validate a file against the given lxml.etree.Schema.
- return True on success, False on failure """
- if schema is None:
- # if no schema object was provided, instantiate one
- try:
- schema = lxml.etree.XMLSchema(lxml.etree.parse(schemafile))
- except:
- self.LintError("schema-failed-to-parse",
- "Failed to process schema %s" % schemafile)
- return False
-
- try:
- datafile = lxml.etree.parse(filename)
- except SyntaxError:
- lint = Popen(["xmllint", filename], stdout=PIPE, stderr=STDOUT)
- self.LintError("xml-failed-to-parse",
- "%s fails to parse:\n%s" % (filename,
- lint.communicate()[0]))
- lint.wait()
- return False
- except IOError:
- self.LintError("xml-failed-to-read",
- "Failed to open file %s" % filename)
- return False
-
- if not schema.validate(datafile):
- cmd = ["xmllint"]
- if self.files is None:
- cmd.append("--xinclude")
- cmd.extend(["--noout", "--schema", schemafile, filename])
- lint = Popen(cmd, stdout=PIPE, stderr=STDOUT)
- output = lint.communicate()[0]
- if lint.wait():
- self.LintError("xml-failed-to-verify",
- "%s fails to verify:\n%s" % (filename, output))
- return False
- return True
-
- def get_filelists(self):
- """ get lists of different kinds of files to validate """
- if self.files is not None:
- listfiles = lambda p: fnmatch.filter(self.files, p % "*")
- else:
- listfiles = lambda p: glob.glob(p % self.config['repo'])
-
- for path in self.filesets.keys():
- if path.startswith("metadata:"):
- mtype = path.split(":")[1]
- self.filelists[path] = self.get_metadata_list(mtype)
- elif path == "info":
- if self.files is not None:
- self.filelists[path] = \
- [f for f in self.files
- if os.path.basename(f) == 'info.xml']
- else: # self.files is None
- self.filelists[path] = []
- for infodir in ['Cfg', 'TGenshi', 'TCheetah']:
- for root, dirs, files in os.walk('%s/%s' %
- (self.config['repo'],
- infodir)):
- self.filelists[path].extend([os.path.join(root, f)
- for f in files
- if f == 'info.xml'])
- else:
- self.filelists[path] = listfiles(path)
-
- self.filelists['props'] = listfiles("%s/Properties/*.xml")
- all_metadata = listfiles("%s/Metadata/*.xml")
-
- # if there are other files in Metadata that aren't xincluded
- # from clients.xml or groups.xml, we can't verify them. warn
- # about those.
- for fname in all_metadata:
- if (fname not in self.filelists['metadata:groups'] and
- fname not in self.filelists['metadata:clients']):
- self.LintError("broken-xinclude-chain",
- "Broken XInclude chain: Could not determine file type of %s" % fname)
-
- def get_metadata_list(self, mtype):
- """ get all metadata files for the specified type (clients or
- group) """
- if self.files is not None:
- rv = fnmatch.filter(self.files, "*/Metadata/%s.xml" % mtype)
- else:
- rv = glob.glob("%s/Metadata/%s.xml" % (self.config['repo'], mtype))
-
- # attempt to follow XIncludes. if the top-level files aren't
- # listed in self.files, though, there's really nothing we can
- # do to guess what a file in Metadata is
- if rv:
- try:
- rv.extend(self.follow_xinclude(rv[0]))
- except lxml.etree.XMLSyntaxError:
- e = sys.exc_info()[1]
- self.LintError("xml-failed-to-parse",
- "%s fails to parse:\n%s" % (rv[0], e))
-
-
- return rv
-
- def follow_xinclude(self, xfile):
- """ follow xincludes in the given file """
- xdata = lxml.etree.parse(xfile)
- included = set([ent.get('href') for ent in
- xdata.findall('./{http://www.w3.org/2001/XInclude}include')])
- rv = []
-
- while included:
- try:
- filename = included.pop()
- except KeyError:
- continue
-
- path = os.path.join(os.path.dirname(xfile), filename)
- if self.HandlesFile(path):
- rv.append(path)
- groupdata = lxml.etree.parse(path)
- [included.add(el.get('href'))
- for el in
- groupdata.findall('./{http://www.w3.org/2001/XInclude}include')]
- included.discard(filename)
-
- return rv
-
diff --git a/src/lib/Server/Lint/__init__.py b/src/lib/Server/Lint/__init__.py
deleted file mode 100644
index 4d6df8c8f..000000000
--- a/src/lib/Server/Lint/__init__.py
+++ /dev/null
@@ -1,218 +0,0 @@
-__revision__ = '$Revision$'
-
-__all__ = ['Bundles',
- 'Comments',
- 'Duplicates',
- 'InfoXML',
- 'MergeFiles',
- 'Pkgmgr',
- 'RequiredAttrs',
- 'Validate',
- 'Genshi',
- 'Deltas']
-
-import logging
-import os
-import sys
-from copy import copy
-import textwrap
-import lxml.etree
-import Bcfg2.Logger
-import fcntl
-import termios
-import struct
-
-def _ioctl_GWINSZ(fd):
- try:
- cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
- except:
- return None
- return cr
-
-def get_termsize():
- """ get a tuple of (width, height) giving the size of the terminal """
- if not sys.stdout.isatty():
- return None
- cr = _ioctl_GWINSZ(0) or _ioctl_GWINSZ(1) or _ioctl_GWINSZ(2)
- if not cr:
- try:
- fd = os.open(os.ctermid(), os.O_RDONLY)
- cr = _ioctl_GWINSZ(fd)
- os.close(fd)
- except:
- pass
- if not cr:
- try:
- cr = (os.environ['LINES'], os.environ['COLUMNS'])
- except KeyError:
- return None
- return int(cr[1]), int(cr[0])
-
-class Plugin (object):
- """ base class for ServerlessPlugin and ServerPlugin """
-
- def __init__(self, config, errorhandler=None, files=None):
- self.files = files
- self.config = config
- self.logger = logging.getLogger('bcfg2-lint')
- if errorhandler is None:
- self.errorHandler = ErrorHandler()
- else:
- self.errorHandler = errorhandler
-
- def Run(self):
- """ run the plugin. must be overloaded by child classes """
- pass
-
- def HandlesFile(self, fname):
- """ returns true if the given file should be handled by the
- plugin according to the files list, false otherwise """
- return (self.files is None or
- fname in self.files or
- os.path.join(self.config['repo'], fname) in self.files or
- os.path.abspath(fname) in self.files or
- os.path.abspath(os.path.join(self.config['repo'],
- fname)) in self.files)
-
- def LintError(self, err, msg):
- self.errorHandler.dispatch(err, msg)
-
- def RenderXML(self, element):
- """render an XML element for error output -- line number
- prefixed, no children"""
- xml = None
- if len(element) or element.text:
- el = copy(element)
- if el.text:
- el.text = '...'
- [el.remove(c) for c in el.iterchildren()]
- xml = lxml.etree.tostring(el).strip()
- else:
- xml = lxml.etree.tostring(element).strip()
- return " line %s: %s" % (element.sourceline, xml)
-
-
-class ErrorHandler (object):
- # how to handle different errors by default
- _errors = {"no-infoxml":"warning",
- "paranoid-false":"warning",
- "bundle-not-found":"error",
- "inconsistent-bundle-name":"warning",
- "group-tag-not-allowed":"error",
- "unexpanded-keywords":"warning",
- "keywords-not-found":"warning",
- "comments-not-found":"warning",
- "broken-xinclude-chain":"warning",
- "duplicate-client":"error",
- "duplicate-group":"error",
- "duplicate-package":"error",
- "multiple-default-groups":"error",
- "required-infoxml-attrs-missing":"error",
- "unknown-entry-type":"error",
- "required-attrs-missing":"error",
- "extra-attrs":"warning",
- "schema-failed-to-parse":"warning",
- "properties-schema-not-found":"warning",
- "xml-failed-to-parse":"error",
- "xml-failed-to-read":"error",
- "xml-failed-to-verify":"error",
- "merge-cfg":"warning",
- "merge-probes":"warning",
- "input-output-error":"error",
- "genshi-syntax-error":"error",
- "pattern-fails-to-initialize":"error",
- "cat-file-used":"warning",
- "diff-file-used":"warning"}
-
- def __init__(self, config=None):
- self.errors = 0
- self.warnings = 0
-
- self.logger = logging.getLogger('bcfg2-lint')
-
- termsize = get_termsize()
- if termsize is not None:
- twrap = textwrap.TextWrapper(initial_indent=" ",
- subsequent_indent=" ",
- width=termsize[0])
- self._wrapper = twrap.wrap
- else:
- self._wrapper = lambda s: [s]
-
- self._handlers = {}
- if config is not None:
- for err, action in config.items():
- if "warn" in action:
- self._handlers[err] = self.warn
- elif "err" in action:
- self._handlers[err] = self.error
- else:
- self._handlers[err] = self.debug
-
- for err, action in self._errors.items():
- if err not in self._handlers:
- if "warn" in action:
- self._handlers[err] = self.warn
- elif "err" in action:
- self._handlers[err] = self.error
- else:
- self._handlers[err] = self.debug
-
- def dispatch(self, err, msg):
- if err in self._handlers:
- self._handlers[err](msg)
- self.logger.debug(" (%s)" % err)
- else:
- # assume that it's an error, but complain
- self.error(msg)
- self.logger.warning("Unknown error %s" % err)
-
- def error(self, msg):
- """ log an error condition """
- self.errors += 1
- self._log(msg, self.logger.error, prefix="ERROR: ")
-
- def warn(self, msg):
- """ log a warning condition """
- self.warnings += 1
- self._log(msg, self.logger.warning, prefix="WARNING: ")
-
- def debug(self, msg):
- """ log a silent/debug condition """
- self._log(msg, self.logger.debug)
-
- def _log(self, msg, logfunc, prefix=""):
- # a message may itself consist of multiple lines. wrap() will
- # elide them all into a single paragraph, which we don't want.
- # so we split the message into its paragraphs and wrap each
- # paragraph individually. this means, unfortunately, that we
- # lose textwrap's built-in initial indent functionality,
- # because we want to only treat the very first line of the
- # first paragraph specially. so we do some silliness.
- rawlines = msg.splitlines()
- firstline = True
- for rawline in rawlines:
- lines = self._wrapper(rawline)
- for line in lines:
- if firstline:
- logfunc(prefix + line.lstrip())
- firstline = False
- else:
- logfunc(line)
-
-
-class ServerlessPlugin (Plugin):
- """ base class for plugins that are run before the server starts
- up (i.e., plugins that check things that may prevent the server
- from starting up) """
- pass
-
-
-class ServerPlugin (Plugin):
- """ base class for plugins that check things that require the
- running Bcfg2 server """
- def __init__(self, lintCore, config, **kwargs):
- Plugin.__init__(self, config, **kwargs)
- self.core = lintCore
- self.logger = self.core.logger
- self.metadata = self.core.metadata