summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Lint
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/Lint')
-rw-r--r--src/lib/Bcfg2/Server/Lint/Bundles.py54
-rw-r--r--src/lib/Bcfg2/Server/Lint/Comments.py5
-rw-r--r--src/lib/Bcfg2/Server/Lint/Deltas.py25
-rw-r--r--src/lib/Bcfg2/Server/Lint/Duplicates.py5
-rwxr-xr-xsrc/lib/Bcfg2/Server/Lint/Genshi.py1
-rw-r--r--src/lib/Bcfg2/Server/Lint/GroupNames.py78
-rw-r--r--src/lib/Bcfg2/Server/Lint/GroupPatterns.py35
-rw-r--r--src/lib/Bcfg2/Server/Lint/InfoXML.py41
-rw-r--r--src/lib/Bcfg2/Server/Lint/Pkgmgr.py38
-rw-r--r--src/lib/Bcfg2/Server/Lint/RequiredAttrs.py163
-rw-r--r--src/lib/Bcfg2/Server/Lint/TemplateHelper.py64
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py62
-rw-r--r--src/lib/Bcfg2/Server/Lint/__init__.py10
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)