From fe9b75662276326898458a68744b920ebd9d46b8 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 19 Sep 2011 11:04:03 -0400 Subject: added Defaults plugin --- src/lib/Server/Lint/MergeFiles.py | 9 ++-- src/lib/Server/Lint/RequiredAttrs.py | 94 +++++++++++++++++++++++------------- src/lib/Server/Lint/Validate.py | 18 +++---- src/lib/Server/Lint/__init__.py | 2 +- src/lib/Server/Plugin.py | 8 +-- src/lib/Server/Plugins/Defaults.py | 51 +++++++++++++++++++ 6 files changed, 130 insertions(+), 52 deletions(-) create mode 100644 src/lib/Server/Plugins/Defaults.py (limited to 'src') diff --git a/src/lib/Server/Lint/MergeFiles.py b/src/lib/Server/Lint/MergeFiles.py index 27e7aa99a..52fea3d9b 100644 --- a/src/lib/Server/Lint/MergeFiles.py +++ b/src/lib/Server/Lint/MergeFiles.py @@ -1,7 +1,6 @@ import os from copy import deepcopy from difflib import SequenceMatcher -import Bcfg2.Options import Bcfg2.Server.Lint class MergeFiles(Bcfg2.Server.Lint.ServerPlugin): @@ -27,10 +26,10 @@ class MergeFiles(Bcfg2.Server.Lint.ServerPlugin): 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])) + 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: diff --git a/src/lib/Server/Lint/RequiredAttrs.py b/src/lib/Server/Lint/RequiredAttrs.py index a94bbb3ed..4d4e99f32 100644 --- a/src/lib/Server/Lint/RequiredAttrs.py +++ b/src/lib/Server/Lint/RequiredAttrs.py @@ -10,20 +10,33 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): def __init__(self, *args, **kwargs): Bcfg2.Server.Lint.ServerPlugin.__init__(self, *args, **kwargs) self.required_attrs = { - 'device': ['name', 'owner', 'group', 'dev_type'], - 'directory': ['name', 'owner', 'group', 'perms'], - 'file': ['name', 'owner', 'group', 'perms'], - 'hardlink': ['name', 'to'], - 'symlink': ['name', 'to'], - 'ignore': ['name'], - 'nonexistent': ['name'], - 'permissions': ['name', 'owner', 'group', 'perms'], - 'vcs': ['vcstype', 'revision', 'sourceurl']} + '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_rules() - self.check_bundles() 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 """ @@ -70,22 +83,34 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): except (lxml.etree.XMLSyntaxError, AttributeError): xdata = lxml.etree.parse(bundle.template.filepath).getroot() - for path in xdata.xpath("//BoundPath"): + 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): - pathname = entry.get('name') - pathtype = entry.get('type') - pathset = set(entry.attrib.keys()) - try: - required_attrs = set(self.required_attrs[pathtype] + ['type']) - except KeyError: - self.LintError("unknown-path-type", - "Unknown path type %s: %s" % - (pathtype, self.RenderXML(entry))) - return + 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') @@ -93,17 +118,20 @@ class RequiredAttrs(Bcfg2.Server.Lint.ServerPlugin): # check if major/minor are specified required_attrs |= set(['major', 'minor']) - if pathtype == 'file' and not entry.text: - self.LintError("required-attrs-missing", - "Text missing for %s %s in %s: %s" % - (entry.tag, pathname, filename, - self.RenderXML(entry))) + if '__text__' in required_attrs: + required_attrs.pop('__text__') + if not entry.text: + self.LintError("required-attrs-missing", + "Text missing for %s %s in %s: %s" % + (entry.tag, name, filename, + self.RenderXML(entry))) - if not pathset.issuperset(required_attrs): + if not attrs.issuperset(required_attrs): self.LintError("required-attrs-missing", - "The required attributes %s are missing for %s %sin %s:\n%s" % - (",".join([attr - for attr in - required_attrs.difference(pathset)]), - entry.tag, pathname, filename, + "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 index ebf621c22..19fd61d25 100644 --- a/src/lib/Server/Lint/Validate.py +++ b/src/lib/Server/Lint/Validate.py @@ -5,7 +5,6 @@ import os from subprocess import Popen, PIPE, STDOUT import sys -import Bcfg2.Options import Bcfg2.Server.Lint class Validate(Bcfg2.Server.Lint.ServerlessPlugin): @@ -21,6 +20,7 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): "%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", @@ -45,21 +45,21 @@ 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(schemaname % - schemadir)) + schema = lxml.etree.XMLSchema(lxml.etree.parse(schemafile)) except IOError: e = sys.exc_info()[1] - self.LintError("input-output-error", e.message) + self.LintError("input-output-error", str(e)) continue - except: + except lxml.etree.XMLSchemaParseError: + e = sys.exc_info()[1] self.LintError("schema-failed-to-parse", - "Failed to process schema %s" % - (schemaname % schemadir)) + "Failed to process schema %s: %s" % + (schemafile, e)) continue for filename in filelist: - self.validate(filename, schemaname % schemadir, - schema=schema) + self.validate(filename, schemafile, schema=schema) self.check_properties() diff --git a/src/lib/Server/Lint/__init__.py b/src/lib/Server/Lint/__init__.py index f15c90557..f47059ac4 100644 --- a/src/lib/Server/Lint/__init__.py +++ b/src/lib/Server/Lint/__init__.py @@ -107,7 +107,7 @@ class ErrorHandler (object): "duplicate-package":"error", "multiple-default-groups":"error", "required-infoxml-attrs-missing":"error", - "unknown-path-type":"error", + "unknown-entry-type":"error", "required-attrs-missing":"error", "extra-attrs":"warning", "schema-failed-to-parse":"warning", diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index bf55ad271..36423e5cd 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -789,10 +789,10 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): def get_attrs(self, entry, metadata): """ get a list of attributes to add to the entry during the bind """ - if False in [src.Cache(metadata) - for src in list(self.entries.values())]: - self.logger.error("Called before data loaded") - raise PluginExecutionError + for src in list(self.entries.values()): + if src.Cache(metadata) == False: + self.logger.error("Called before data loaded") + raise PluginExecutionError matching = [src for src in list(self.entries.values()) if (src.cache and entry.tag in src.cache[1] and diff --git a/src/lib/Server/Plugins/Defaults.py b/src/lib/Server/Plugins/Defaults.py new file mode 100644 index 000000000..23104946e --- /dev/null +++ b/src/lib/Server/Plugins/Defaults.py @@ -0,0 +1,51 @@ +"""This generator provides rule-based entry mappings.""" +__revision__ = '$Revision$' + +import re +import Bcfg2.Server.Plugin +import Bcfg2.Server.Plugins.Rules + +class Defaults(Bcfg2.Server.Plugins.Rules.Rules, + Bcfg2.Server.Plugin.StructureValidator): + """Set default attributes on bound entries""" + name = 'Defaults' + __version__ = '$Id$' + __author__ = 'bcfg-dev@mcs.anl.gov' + + # Rules is a Generator that happens to implement all of the + # functionality we want, so we overload it, but Defaults should + # _not_ handle any entries; it does its stuff in the structure + # validation phase. so we overload Handle(s)Entry and HandleEvent + # to ensure that Defaults handles no entries, even though it's a + # Generator. + + def HandlesEntry(self, entry, metadata): + return False + + def HandleEntry(self, entry, metadata): + raise PluginExecutionError + + def HandleEvent(self, event): + Bcfg2.Server.Plugin.XMLDirectoryBacked.HandleEvent(self, event) + + def validate_structures(self, metadata, structures): + """ Apply defaults """ + for struct in structures: + for entry in struct.iter(): + if entry.tag.startswith("Bound"): + is_bound = True + entry.tag = entry.tag[5:] + else: + is_bound = False + try: + try: + self.BindEntry(entry, metadata) + except Bcfg2.Server.Plugin.PluginExecutionError: + # either no matching defaults (which is okay), + # or multiple matching defaults (which is not + # okay, but is logged). either way, we don't + # care about the error. + pass + finally: + if is_bound: + entry.tag = "Bound" + entry.tag -- cgit v1.2.3-1-g7c22