diff options
Diffstat (limited to 'src/lib/Bcfg2/Server/Lint')
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Bundles.py | 54 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Comments.py | 5 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Deltas.py | 25 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Duplicates.py | 5 | ||||
-rwxr-xr-x | src/lib/Bcfg2/Server/Lint/Genshi.py | 1 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/GroupNames.py | 78 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/GroupPatterns.py | 35 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/InfoXML.py | 41 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Pkgmgr.py | 38 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/RequiredAttrs.py | 163 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/TemplateHelper.py | 64 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/Validate.py | 62 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Lint/__init__.py | 10 |
13 files changed, 277 insertions, 304 deletions
diff --git a/src/lib/Bcfg2/Server/Lint/Bundles.py b/src/lib/Bcfg2/Server/Lint/Bundles.py deleted file mode 100644 index e6b6307f2..000000000 --- a/src/lib/Bcfg2/Server/Lint/Bundles.py +++ /dev/null @@ -1,54 +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) - - @classmethod - def Errors(cls): - return {"bundle-not-found":"error", - "inconsistent-bundle-name":"warning"} - - 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)) diff --git a/src/lib/Bcfg2/Server/Lint/Comments.py b/src/lib/Bcfg2/Server/Lint/Comments.py index f5d0e265f..59d18fc57 100644 --- a/src/lib/Bcfg2/Server/Lint/Comments.py +++ b/src/lib/Bcfg2/Server/Lint/Comments.py @@ -1,6 +1,7 @@ -import os.path +import os import lxml.etree import Bcfg2.Server.Lint +from Bcfg2.Server import XI, XI_NAMESPACE from Bcfg2.Server.Plugins.Cfg.CfgPlaintextGenerator import CfgPlaintextGenerator from Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator import CfgGenshiGenerator from Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator import CfgCheetahGenerator @@ -186,7 +187,7 @@ class Comments(Bcfg2.Server.Lint.ServerPlugin): 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'): + 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) diff --git a/src/lib/Bcfg2/Server/Lint/Deltas.py b/src/lib/Bcfg2/Server/Lint/Deltas.py deleted file mode 100644 index 114f2e348..000000000 --- a/src/lib/Bcfg2/Server/Lint/Deltas.py +++ /dev/null @@ -1,25 +0,0 @@ -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 """ - - def Run(self): - """ run plugin """ - if 'Cfg' in self.core.plugins: - cfg = self.core.plugins['Cfg'] - 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, 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/Bcfg2/Server/Lint/Duplicates.py b/src/lib/Bcfg2/Server/Lint/Duplicates.py index ee6b7a2e6..60a02ffb9 100644 --- a/src/lib/Bcfg2/Server/Lint/Duplicates.py +++ b/src/lib/Bcfg2/Server/Lint/Duplicates.py @@ -1,6 +1,7 @@ -import os.path +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. """ @@ -80,7 +81,7 @@ class Duplicates(Bcfg2.Server.Lint.ServerPlugin): 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'): + 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) diff --git a/src/lib/Bcfg2/Server/Lint/Genshi.py b/src/lib/Bcfg2/Server/Lint/Genshi.py index b6007161e..74142b446 100755 --- a/src/lib/Bcfg2/Server/Lint/Genshi.py +++ b/src/lib/Bcfg2/Server/Lint/Genshi.py @@ -1,3 +1,4 @@ +import sys import genshi.template import Bcfg2.Server.Lint diff --git a/src/lib/Bcfg2/Server/Lint/GroupNames.py b/src/lib/Bcfg2/Server/Lint/GroupNames.py new file mode 100644 index 000000000..5df98a30e --- /dev/null +++ b/src/lib/Bcfg2/Server/Lint/GroupNames.py @@ -0,0 +1,78 @@ +import os +import re +import Bcfg2.Server.Lint +try: + from Bcfg2.Server.Plugins.Bundler import BundleTemplateFile + has_genshi = True +except ImportError: + has_genshi = False + +class GroupNames(Bcfg2.Server.Lint.ServerPlugin): + """ ensure that all named groups are valid group names """ + pattern = r'\S+$' + valid = re.compile(r'^' + pattern) + + def Run(self): + self.check_metadata() + if 'Rules' in self.core.plugins: + self.check_rules() + if 'Bundler' in self.core.plugins: + self.check_bundles() + if 'GroupPatterns' in self.core.plugins: + self.check_grouppatterns() + if 'Cfg' in self.core.plugins: + self.check_cfg() + + @classmethod + def Errors(cls): + return {"invalid-group-name": "error"} + + def check_rules(self): + for rules in self.core.plugins['Rules'].entries.values(): + if not self.HandlesFile(rules.name): + continue + xdata = rules.pnode.data + self.check_entries(xdata.xpath("//Group"), + os.path.join(self.config['repo'], rules.name)) + + def check_bundles(self): + """ check bundles for BoundPath entries with missing attrs """ + for bundle in self.core.plugins['Bundler'].entries.values(): + if (self.HandlesFile(bundle.name) and + (not has_genshi or + not isinstance(bundle, BundleTemplateFile))): + self.check_entries(bundle.xdata.xpath("//Group"), + bundle.name) + + def check_metadata(self): + self.check_entries(self.metadata.groups_xml.xdata.xpath("//Group"), + os.path.join(self.config['repo'], + self.metadata.groups_xml.name)) + + def check_grouppatterns(self): + cfg = self.core.plugins['GroupPatterns'].config + if not self.HandlesFile(cfg.name): + return + for grp in cfg.xdata.xpath('//GroupPattern/Group'): + if not self.valid.search(grp.text): + self.LintError("invalid-group-name", + "Invalid group name in %s: %s" % + (cfg.name, self.RenderXML(grp, keep_text=True))) + + def check_cfg(self): + for root, dirs, files in os.walk(self.core.plugins['Cfg'].data): + for fname in files: + basename = os.path.basename(root) + if (re.search(r'^%s\.G\d\d_' % basename, fname) and + not re.search(r'^%s\.G\d\d_' % basename + self.pattern, + fname)): + self.LintError("invalid-group-name", + "Invalid group name referenced in %s" % + os.path.join(root, fname)) + + def check_entries(self, entries, fname): + for grp in entries: + if not self.valid.search(grp.get("name")): + self.LintError("invalid-group-name", + "Invalid group name in %s: %s" % + (fname, self.RenderXML(grp))) diff --git a/src/lib/Bcfg2/Server/Lint/GroupPatterns.py b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py deleted file mode 100644 index 431ba4056..000000000 --- a/src/lib/Bcfg2/Server/Lint/GroupPatterns.py +++ /dev/null @@ -1,35 +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') - - @classmethod - def Errors(cls): - return {"pattern-fails-to-initialize":"error"} - - 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/Bcfg2/Server/Lint/InfoXML.py b/src/lib/Bcfg2/Server/Lint/InfoXML.py index db6aeea73..5e4e21e18 100644 --- a/src/lib/Bcfg2/Server/Lint/InfoXML.py +++ b/src/lib/Bcfg2/Server/Lint/InfoXML.py @@ -1,28 +1,41 @@ -import os.path +import os import Bcfg2.Options import Bcfg2.Server.Lint from Bcfg2.Server.Plugins.Cfg.CfgInfoXML import CfgInfoXML +from Bcfg2.Server.Plugins.Cfg.CfgLegacyInfo import CfgLegacyInfo 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): - found = False - for entry in entryset.entries.values(): - if isinstance(entry, CfgInfoXML): - self.check_infoxml(infoxml_fname, - entry.infoxml.pnode.data) - found = True - if not found: - self.LintError("no-infoxml", - "No info.xml found for %s" % filename) + if 'Cfg' not in self.core.plugins: + return + + for filename, entryset in self.core.plugins['Cfg'].entries.items(): + infoxml_fname = os.path.join(entryset.path, "info.xml") + if self.HandlesFile(infoxml_fname): + found = False + for entry in entryset.entries.values(): + if isinstance(entry, CfgInfoXML): + self.check_infoxml(infoxml_fname, + entry.infoxml.pnode.data) + found = True + if not found: + self.LintError("no-infoxml", + "No info.xml found for %s" % filename) + + for entry in entryset.entries.values(): + if isinstance(entry, CfgLegacyInfo): + if not self.HandlesFile(entry.path): + continue + self.LintError("deprecated-info-file", + "Deprecated %s file found at %s" % + (os.path.basename(entry.name), + entry.path)) @classmethod def Errors(cls): return {"no-infoxml":"warning", + "deprecated-info-file":"warning", "paranoid-false":"warning", "broken-xinclude-chain":"warning", "required-infoxml-attrs-missing":"error"} diff --git a/src/lib/Bcfg2/Server/Lint/Pkgmgr.py b/src/lib/Bcfg2/Server/Lint/Pkgmgr.py deleted file mode 100644 index ceb46238a..000000000 --- a/src/lib/Bcfg2/Server/Lint/Pkgmgr.py +++ /dev/null @@ -1,38 +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) - - @classmethod - def Errors(cls): - return {"duplicate-packages":"error"} diff --git a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py index 6f76cf2db..fcb7c6c28 100644 --- a/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py +++ b/src/lib/Bcfg2/Server/Lint/RequiredAttrs.py @@ -1,32 +1,105 @@ -import os.path +import os +import re import lxml.etree import Bcfg2.Server.Lint +import Bcfg2.Client.Tools.POSIX +import Bcfg2.Client.Tools.VCS from Bcfg2.Server.Plugins.Packages import Apt, Yum +try: + from Bcfg2.Server.Plugins.Bundler import BundleTemplateFile + has_genshi = True +except ImportError: + has_genshi = False + +# format verifying functions +def is_filename(val): + return val.startswith("/") and len(val) > 1 + +def is_selinux_type(val): + return re.match(r'^[a-z_]+_t', val) + +def is_selinux_user(val): + return re.match(r'^[a-z_]+_u', val) + +def is_octal_mode(val): + return re.match(r'[0-7]{3,4}', val) + +def is_username(val): + return re.match(r'^([a-z]\w{0,30}|\d+)$', val) + +def is_device_mode(val): + try: + # checking upper bound seems like a good way to discover some + # obscure OS with >8-bit device numbers + return int(val) > 0 + except: + return False 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']} + self.required_attrs = dict( + Path=dict( + device=dict(name=is_filename, owner=is_username, + group=is_username, + dev_type=lambda v: \ + v in Bcfg2.Client.Tools.POSIX.device_map), + directory=dict(name=is_filename, owner=is_username, + group=is_username, perms=is_octal_mode), + file=dict(name=is_filename, owner=is_username, + group=is_username, perms=is_octal_mode, + __text__=None), + hardlink=dict(name=is_filename, to=is_filename), + symlink=dict(name=is_filename, to=is_filename), + ignore=dict(name=is_filename), + nonexistent=dict(name=is_filename), + permissions=dict(name=is_filename, owner=is_username, + group=is_username, perms=is_octal_mode), + vcs=dict(vcstype=lambda v: (v != 'Path' and + hasattr(Bcfg2.Client.Tools.VCS, + "Install%s" % v)), + revision=None, sourceurl=None)), + Service={ + "chkconfig": dict(name=None), + "deb": dict(name=None), + "rc-update": dict(name=None), + "smf": dict(name=None, FMRI=None), + "upstart": dict(name=None)}, + Action={None: dict(name=None, + timing=lambda v: v in ['pre', 'post', 'both'], + when=lambda v: v in ['modified', 'always'], + status=lambda v: v in ['ignore', 'check'], + command=None)}, + ACL=dict( + default=dict(scope=lambda v: v in ['user', 'group'], + perms=lambda v: re.match('^([0-7]|[rwx\-]{0,3}', + v)), + access=dict(scope=lambda v: v in ['user', 'group'], + perms=lambda v: re.match('^([0-7]|[rwx\-]{0,3}', + v)), + mask=dict(perms=lambda v: re.match('^([0-7]|[rwx\-]{0,3}', v))), + Package={None: dict(name=None)}, + SELinux=dict( + boolean=dict(name=None, + value=lambda v: v in ['on', 'off']), + module=dict(name=None, __text__=None), + port=dict(name=lambda v: re.match(r'^\d+(-\d+)?/(tcp|udp)', v), + selinuxtype=is_selinux_type), + fcontext=dict(name=None, selinuxtype=is_selinux_type), + node=dict(name=lambda v: "/" in v, + selinuxtype=is_selinux_type, + proto=lambda v: v in ['ipv6', 'ipv4']), + login=dict(name=is_username, + selinuxuser=is_selinux_user), + user=dict(name=is_selinux_user, + roles=lambda v: all(is_selinux_user(u) + for u in " ".split(v)), + prefix=None), + interface=dict(name=None, selinuxtype=is_selinux_type), + permissive=dict(name=is_selinux_type)) + ) def Run(self): self.check_packages() @@ -42,9 +115,9 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): return {"unknown-entry-type":"error", "unknown-entry-tag":"error", "required-attrs-missing":"error", + "required-attr-format":"error", "extra-attrs":"warning"} - def check_packages(self): """ check package sources for Source entries with missing attrs """ if 'Packages' in self.core.plugins: @@ -85,13 +158,17 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): """ 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() + if (self.HandlesFile(bundle.name) and + (not has_genshi or + not isinstance(bundle, BundleTemplateFile))): + 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) + 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 """ @@ -103,43 +180,55 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): if tag not in self.required_attrs: self.LintError("unknown-entry-tag", "Unknown entry tag '%s': %s" % - (entry.tag, self.RenderXML(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']) + required_attrs = self.required_attrs[tag][etype] 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]) + required_attrs = 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']) + required_attrs['major'] = is_device_mode + required_attrs['minor'] = is_device_mode + + if tag == 'ACL' and 'scope' in required_attrs: + required_attrs[entry.get('scope')] = is_username if '__text__' in required_attrs: - required_attrs.remove('__text__') + del required_attrs['__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, + (tag, name, filename, self.RenderXML(entry))) - if not attrs.issuperset(required_attrs): + if not attrs.issuperset(required_attrs.keys()): self.LintError("required-attrs-missing", "The following required attribute(s) are " "missing for %s %s in %s: %s\n%s" % - (entry.tag, name, filename, + (tag, name, filename, ", ".join([attr for attr in - required_attrs.difference(attrs)]), + set(required_attrs.keys()).difference(attrs)]), self.RenderXML(entry))) + + for attr, fmt in required_attrs.items(): + if fmt and attr in attrs and not fmt(entry.attrib[attr]): + self.LintError("required-attr-format", + "The %s attribute of %s %s in %s is " + "malformed\n%s" % + (attr, tag, name, filename, + self.RenderXML(entry))) + diff --git a/src/lib/Bcfg2/Server/Lint/TemplateHelper.py b/src/lib/Bcfg2/Server/Lint/TemplateHelper.py deleted file mode 100644 index be270a59c..000000000 --- a/src/lib/Bcfg2/Server/Lint/TemplateHelper.py +++ /dev/null @@ -1,64 +0,0 @@ -import sys -import imp -import glob -import Bcfg2.Server.Lint -from Bcfg2.Server.Plugins.TemplateHelper import HelperModule - -class TemplateHelper(Bcfg2.Server.Lint.ServerlessPlugin): - """ find duplicate Pkgmgr entries with the same priority """ - def __init__(self, *args, **kwargs): - Bcfg2.Server.Lint.ServerlessPlugin.__init__(self, *args, **kwargs) - hm = HelperModule("foo.py", None, None) - self.reserved_keywords = dir(hm) - - def Run(self): - for helper in glob.glob("%s/TemplateHelper/*.py" % self.config['repo']): - if not self.HandlesFile(helper): - continue - - match = HelperModule._module_name_re.search(helper) - if match: - module_name = match.group(1) - else: - module_name = helper - - try: - module = imp.load_source(module_name, helper) - except: - err = sys.exc_info()[1] - self.LintError("templatehelper-import-error", - "Failed to import %s: %s" % - (helper, err)) - continue - - if not hasattr(module, "__export__"): - self.LintError("templatehelper-no-export", - "%s has no __export__ list" % helper) - continue - elif not isinstance(module.__export__, list): - self.LintError("templatehelper-nonlist-export", - "__export__ is not a list in %s" % helper) - continue - - for sym in module.__export__: - if not hasattr(module, sym): - self.LintError("templatehelper-nonexistent-export", - "%s: exported symbol %s does not exist" % - (helper, sym)) - elif sym in self.reserved_keywords: - self.LintError("templatehelper-reserved-export", - "%s: exported symbol %s is reserved" % - (helper, sym)) - elif sym.startswith("_"): - self.LintError("templatehelper-underscore-export", - "%s: exported symbol %s starts with underscore" % - (helper, sym)) - - @classmethod - def Errors(cls): - return {"templatehelper-import-error":"error", - "templatehelper-no-export":"error", - "templatehelper-nonlist-export":"error", - "templatehelper-nonexistent-export":"error", - "templatehelper-reserved-export":"error", - "templatehelper-underscore-export":"warning"} diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py index 05fedc313..b8bdb4755 100644 --- a/src/lib/Bcfg2/Server/Lint/Validate.py +++ b/src/lib/Bcfg2/Server/Lint/Validate.py @@ -1,10 +1,10 @@ -import fnmatch +import os +import sys import glob +import fnmatch import lxml.etree -import os from subprocess import Popen, PIPE, STDOUT -import sys - +from Bcfg2.Server import XI, XI_NAMESPACE import Bcfg2.Server.Lint class Validate(Bcfg2.Server.Lint.ServerlessPlugin): @@ -22,7 +22,6 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): "%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", @@ -46,20 +45,10 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): 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) + schema = self._load_schema(schemafile) + if schema: + for filename in filelist: + self.validate(filename, schemafile, schema=schema) self.check_properties() @@ -88,11 +77,8 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): 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) + schema = self._load_schema(schemafile) + if not schema: return False try: @@ -187,24 +173,42 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): 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')]) + included = set([el + for el in xdata.findall('./%sinclude' % XI_NAMESPACE)]) rv = [] while included: try: - filename = included.pop() + el = included.pop() except KeyError: continue + filename = el.get("href") path = os.path.join(os.path.dirname(xfile), filename) - if self.HandlesFile(path): + if not os.path.exists(path): + if not el.findall('./%sfallback' % XI_NAMESPACE): + self.LintError("broken-xinclude-chain", + "XInclude %s does not exist in %s: %s" % + (filename, xfile, self.RenderXML(el))) + elif 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')] + groupdata.findall('./%sinclude' % XI_NAMESPACE)] included.discard(filename) return rv + def _load_schema(self, filename): + try: + return lxml.etree.XMLSchema(lxml.etree.parse(filename)) + except IOError: + e = sys.exc_info()[1] + self.LintError("input-output-error", str(e)) + except lxml.etree.XMLSchemaParseError: + e = sys.exc_info()[1] + self.LintError("schema-failed-to-parse", + "Failed to process schema %s: %s" % + (filename, e)) + return None diff --git a/src/lib/Bcfg2/Server/Lint/__init__.py b/src/lib/Bcfg2/Server/Lint/__init__.py index 5d7dd707b..e3b4c8ea7 100644 --- a/src/lib/Bcfg2/Server/Lint/__init__.py +++ b/src/lib/Bcfg2/Server/Lint/__init__.py @@ -81,18 +81,20 @@ class Plugin (object): def LintError(self, err, msg): self.errorhandler.dispatch(err, msg) - def RenderXML(self, element): + def RenderXML(self, element, keep_text=False): """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: + if el.text and not keep_text: el.text = '...' [el.remove(c) for c in el.iterchildren()] - xml = lxml.etree.tostring(el).strip() + xml = lxml.etree.tostring(el, + xml_declaration=False).decode("UTF-8").strip() else: - xml = lxml.etree.tostring(element).strip() + xml = lxml.etree.tostring(element, + xml_declaration=False).decode("UTF-8").strip() return " line %s: %s" % (element.sourceline, xml) |