From d8bbfbdf8b503538fff01bff80c5e6e12bfb44b3 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Tue, 12 Nov 2013 23:48:25 +0100 Subject: Add probes.allowed_groups option to restrict group assignments. --- src/lib/Bcfg2/Server/Plugins/Probes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 84e1638d6..59a73c4aa 100644 --- a/src/lib/Bcfg2/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -204,6 +204,7 @@ class Probes(Bcfg2.Server.Plugin.Probing, err = sys.exc_info()[1] raise Bcfg2.Server.Plugin.PluginInitError(err) + self.allowed_cgroups = core.setup['probe_allowed_groups'] self.probedata = dict() self.cgroups = dict() self.load_data() @@ -391,11 +392,18 @@ class Probes(Bcfg2.Server.Plugin.Probing, if line.split(':')[0] == 'group': newgroup = line.split(':')[1].strip() if newgroup not in cgroups: - cgroups.append(newgroup) + if self._group_allowed(newgroup): + cgroups.append(newgroup) + else: + self.logger.info("Disallowed group assignment %s from %s" + % (newgroup, client.hostname)) dlines.remove(line) dobj = ProbeData("\n".join(dlines)) cprobedata[data.get('name')] = dobj + def _group_allowed(self, group): + return any(r.match(group) for r in self.allowed_cgroups) + def get_additional_groups(self, meta): return self.cgroups.get(meta.hostname, list()) get_additional_groups.__doc__ = \ -- cgit v1.2.3-1-g7c22 From bf3adbb11ef36591d80b9b6f4d9768caf516b4e3 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 14 Nov 2013 09:35:06 -0500 Subject: testsuite: fixed unit tests for Probes allowed_groups option --- src/lib/Bcfg2/Server/Plugins/Probes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 59a73c4aa..0df88d522 100644 --- a/src/lib/Bcfg2/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -395,8 +395,9 @@ class Probes(Bcfg2.Server.Plugin.Probing, if self._group_allowed(newgroup): cgroups.append(newgroup) else: - self.logger.info("Disallowed group assignment %s from %s" - % (newgroup, client.hostname)) + self.logger.info( + "Disallowed group assignment %s from %s" % + (newgroup, client.hostname)) dlines.remove(line) dobj = ProbeData("\n".join(dlines)) cprobedata[data.get('name')] = dobj -- cgit v1.2.3-1-g7c22 From 881205322035f9ca7375ab0c67ab339c430dbe01 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 14 Nov 2013 09:51:59 -0500 Subject: Probes: added missing docstring --- src/lib/Bcfg2/Server/Plugins/Probes.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 0df88d522..fdc047283 100644 --- a/src/lib/Bcfg2/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -403,6 +403,9 @@ class Probes(Bcfg2.Server.Plugin.Probing, cprobedata[data.get('name')] = dobj def _group_allowed(self, group): + """ Determine if the named group can be set as a probe group + by checking the regexes listed in the [probes] groups_allowed + setting """ return any(r.match(group) for r in self.allowed_cgroups) def get_additional_groups(self, meta): -- cgit v1.2.3-1-g7c22 From 504741f24c050684d1707cac057fdd4677afdd64 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 18 Nov 2013 09:19:34 -0500 Subject: bcfg2-lint: resolve XIncludes when parsing XML for validation --- src/lib/Bcfg2/Server/Lint/Validate.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py index c537877a0..ced16770b 100644 --- a/src/lib/Bcfg2/Server/Lint/Validate.py +++ b/src/lib/Bcfg2/Server/Lint/Validate.py @@ -107,9 +107,16 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): :type filename: string :returns: lxml.etree._ElementTree - the parsed data""" try: - return lxml.etree.parse(filename) - except SyntaxError: - lint = Popen(["xmllint", filename], stdout=PIPE, stderr=STDOUT) + xdata = lxml.etree.parse(filename) + if self.files is None: + xdata.xinclude() + return xdata + except (lxml.etree.XIncludeError, SyntaxError): + cmd = ["xmllint", "--noout"] + if self.files is None: + cmd.append("--xinclude") + cmd.append(filename) + lint = Popen(cmd, stdout=PIPE, stderr=STDOUT) self.LintError("xml-failed-to-parse", "%s fails to parse:\n%s" % (filename, lint.communicate()[0])) @@ -141,6 +148,8 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin): if not schema: return False datafile = self.parse(filename) + if not datafile: + return False if not schema.validate(datafile): cmd = ["xmllint"] if self.files is None: -- cgit v1.2.3-1-g7c22 From 0f8d403d1a86cfbfe8222662dc445e16e8f7eff9 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 21 Nov 2013 13:03:04 -0500 Subject: Bundler: Fix parsing XML template output with encoding declaration lxml 3.2.1 complains when you try to parse a unicode (in Python 2) or string (in Python 3) containing an XML document with an encoding declaration. Traceback: ValueError: Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration. This encodes the document as a string (in Python 2) or bytes (in Python 3) to avoid the lxml error. There may be other places this happens, too, although in most other cases we should use lxml.etree.parse() to parse a file, or we parse strings (in Python 2) instead of unicode objects. --- src/lib/Bcfg2/Server/Plugins/Bundler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py index fb327f7ef..58f8f4430 100644 --- a/src/lib/Bcfg2/Server/Plugins/Bundler.py +++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py @@ -53,9 +53,9 @@ if HAS_GENSHI: stream = self.template.generate( metadata=metadata, repo=SETUP['repo']).filter(removecomment) - data = lxml.etree.XML(stream.render('xml', - strip_whitespace=False), - parser=Bcfg2.Server.XMLParser) + data = lxml.etree.XML( + stream.render('xml', strip_whitespace=False).encode(), + parser=Bcfg2.Server.XMLParser) bundlename = os.path.splitext(os.path.basename(self.name))[0] bundle = lxml.etree.Element('Bundle', name=bundlename) for item in self.Match(metadata, data): -- cgit v1.2.3-1-g7c22 From a87c59d6f23b4c4fd4e38e380994f2193ca24588 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 25 Nov 2013 10:48:43 -0500 Subject: bcfg2-admin: Restored missing "bcfg2-admin client add ... attrib=val" functionality --- src/lib/Bcfg2/Server/Admin/Client.py | 23 ++++++++++++++++++++--- src/lib/Bcfg2/Server/Plugins/Metadata.py | 5 +++++ 2 files changed, 25 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Admin/Client.py b/src/lib/Bcfg2/Server/Admin/Client.py index 187ccfd71..4eb67a9de 100644 --- a/src/lib/Bcfg2/Server/Admin/Client.py +++ b/src/lib/Bcfg2/Server/Admin/Client.py @@ -5,6 +5,18 @@ import Bcfg2.Server.Admin from Bcfg2.Server.Plugin import MetadataConsistencyError +def get_attribs(args): + attr_d = {} + for i in args[2:]: + attr, val = i.split('=', 1) + if attr not in ['profile', 'uuid', 'password', 'floating', 'secure', + 'address', 'auth']: + print("Attribute %s unknown" % attr) + raise SystemExit(1) + attr_d[attr] = val + return attr_d + + class Client(Bcfg2.Server.Admin.MetadataCore): """ Create, delete, or list client entries """ __usage__ = "[options] [add|del|list] [attr=val]" @@ -16,14 +28,19 @@ class Client(Bcfg2.Server.Admin.MetadataCore): "Usage: %s" % self.__usage__) if args[0] == 'add': try: - self.metadata.add_client(args[1]) + self.metadata.add_client(args[1], get_attribs(args)) + except MetadataConsistencyError: + self.errExit("Error adding client: %s" % sys.exc_info()[1]) + elif args[0] in ['update', 'up']: + try: + self.metadata.update_client(args[1], get_attribs(args)) except MetadataConsistencyError: - self.errExit("Error in adding client: %s" % sys.exc_info()[1]) + self.errExit("Error updating client: %s" % sys.exc_info()[1]) elif args[0] in ['delete', 'remove', 'del', 'rm']: try: self.metadata.remove_client(args[1]) except MetadataConsistencyError: - self.errExit("Error in deleting client: %s" % + self.errExit("Error deleting client: %s" % sys.exc_info()[1]) elif args[0] in ['list', 'ls']: for client in self.metadata.list_clients(): diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py index 047dd4f4e..343e14162 100644 --- a/src/lib/Bcfg2/Server/Plugins/Metadata.py +++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py @@ -658,6 +658,11 @@ class Metadata(Bcfg2.Server.Plugin.Metadata, if attribs is None: attribs = dict() if self._use_db: + if attribs: + msg = "Metadata does not support setting client attributes " +\ + "with use_database enabled" + self.logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) try: client = MetadataClientModel.objects.get(hostname=client_name) except MetadataClientModel.DoesNotExist: -- cgit v1.2.3-1-g7c22 From bcea1949fa2a84e87c51d128a17a25a70d50ca13 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 25 Nov 2013 11:11:16 -0500 Subject: bcfg2-admin: added missing docstring --- src/lib/Bcfg2/Server/Admin/Client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Admin/Client.py b/src/lib/Bcfg2/Server/Admin/Client.py index 4eb67a9de..325b7ae6e 100644 --- a/src/lib/Bcfg2/Server/Admin/Client.py +++ b/src/lib/Bcfg2/Server/Admin/Client.py @@ -6,6 +6,7 @@ from Bcfg2.Server.Plugin import MetadataConsistencyError def get_attribs(args): + """ Get a list of attributes to set on a client when adding/updating it """ attr_d = {} for i in args[2:]: attr, val = i.split('=', 1) @@ -19,7 +20,7 @@ def get_attribs(args): class Client(Bcfg2.Server.Admin.MetadataCore): """ Create, delete, or list client entries """ - __usage__ = "[options] [add|del|list] [attr=val]" + __usage__ = "[options] [add|del|update|list] [attr=val]" __plugin_whitelist__ = ["Metadata"] def __call__(self, args): -- cgit v1.2.3-1-g7c22 From 16b4744544ab140c4ab9bc733a7dfa76cf4e578c Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 25 Nov 2013 11:24:35 -0500 Subject: Core: Avoid starting server if database is enabled but cannot be used Plugins that use the database often act quite differently depending on whether or not the database is enabled. If we start the server without the database (e.g., the connection failed), then Very Strange Things can happen. --- src/lib/Bcfg2/Server/Core.py | 8 ++++---- src/lib/Bcfg2/Server/Plugin/helpers.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py index 5ec1b5bce..c2cf6b7a4 100644 --- a/src/lib/Bcfg2/Server/Core.py +++ b/src/lib/Bcfg2/Server/Core.py @@ -228,11 +228,11 @@ class BaseCore(object): verbosity=0) self._database_available = True except ImproperlyConfigured: - err = sys.exc_info()[1] - self.logger.error("Django configuration problem: %s" % err) + self.logger.error("Django configuration problem: %s" % + sys.exc_info()[1]) except: - err = sys.exc_info()[1] - self.logger.error("Database update failed: %s" % err) + self.logger.error("Database update failed: %s" % + sys.exc_info()[1]) if do_chown and self._database_available: try: diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index d9e208746..f39609a5b 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -16,7 +16,7 @@ from Bcfg2.Compat import CmpMixin, wraps from Bcfg2.Server.Plugin.base import Debuggable, Plugin from Bcfg2.Server.Plugin.interfaces import Generator from Bcfg2.Server.Plugin.exceptions import SpecificityError, \ - PluginExecutionError + PluginExecutionError, PluginInitError try: import django # pylint: disable=W0611 @@ -131,6 +131,18 @@ class DatabaseBacked(Plugin): #: conform to the possible values that function can handle. option = "use_database" + def __init__(self, core, datastore): + Plugin.__init__(self, core, datastore) + use_db = self.core.setup.cfp.getboolean(self.section, + self.option, + default=False) + if use_db and not HAS_DJANGO: + raise PluginInitError("%s is True but Django not found" % + self.option) + elif use_db and not self.core.database_available: + raise PluginInitError("%s is True but the database is unavailable " + "due to prior errors" % self.option) + def _section(self): """ The section to look in for :attr:`DatabaseBacked.option` """ @@ -146,10 +158,7 @@ class DatabaseBacked(Plugin): default=False) if use_db and HAS_DJANGO and self.core.database_available: return True - elif not use_db: - return False else: - self.logger.error("%s is true but django not found" % self.option) return False @property -- cgit v1.2.3-1-g7c22 From 1c7488e85200e50429b1248d36a6815bedc49f02 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 25 Nov 2013 12:55:50 -0500 Subject: testsuite: fixed unit tests for database fixes --- src/lib/Bcfg2/Server/Plugin/helpers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index f39609a5b..be9c9e8ae 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -137,11 +137,12 @@ class DatabaseBacked(Plugin): self.option, default=False) if use_db and not HAS_DJANGO: - raise PluginInitError("%s is True but Django not found" % - self.option) + raise PluginInitError("%s.%s is True but Django not found" % + (self.section, self.option)) elif use_db and not self.core.database_available: - raise PluginInitError("%s is True but the database is unavailable " - "due to prior errors" % self.option) + raise PluginInitError("%s.%s is True but the database is " + "unavailable due to prior errors" % + (self.section, self.option)) def _section(self): """ The section to look in for :attr:`DatabaseBacked.option` -- cgit v1.2.3-1-g7c22 From 2695e7a9af097596527edb52a722d17ea44601cc Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Sun, 8 Dec 2013 21:21:11 -0500 Subject: Cfg: let EncryptedGenerator load setup object whenever the plugin is imported Previously, if CfgEncryptedGenerator was imported before the Cfg object was instantiated, it would finalize the Bcfg2.Server.Plugins.Cfg.SETUP object with a value of None, and would be unable to access the options dict. --- src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py index 3b4703ddb..cf7eae75b 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py @@ -1,8 +1,9 @@ """ CfgEncryptedGenerator lets you encrypt your plaintext :ref:`server-plugins-generators-cfg` files on the server. """ +import Bcfg2.Server.Plugins.Cfg from Bcfg2.Server.Plugin import PluginExecutionError -from Bcfg2.Server.Plugins.Cfg import CfgGenerator, SETUP +from Bcfg2.Server.Plugins.Cfg import CfgGenerator try: from Bcfg2.Encryption import bruteforce_decrypt, EVPError, \ get_algorithm @@ -34,8 +35,10 @@ class CfgEncryptedGenerator(CfgGenerator): return # todo: let the user specify a passphrase by name try: - self.data = bruteforce_decrypt(self.data, setup=SETUP, - algorithm=get_algorithm(SETUP)) + self.data = bruteforce_decrypt( + self.data, + setup=Bcfg2.Server.Plugins.Cfg.SETUP, + algorithm=get_algorithm(Bcfg2.Server.Plugins.Cfg.SETUP)) except EVPError: raise PluginExecutionError("Failed to decrypt %s" % self.name) handle_event.__doc__ = CfgGenerator.handle_event.__doc__ -- cgit v1.2.3-1-g7c22 From 91558c72f6905991c7fb3f24057ab9e41ecce434 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Sun, 8 Dec 2013 21:21:42 -0500 Subject: XMLSrc: Load XML in one step instead of separate read and parse --- src/lib/Bcfg2/Server/Plugin/helpers.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index be9c9e8ae..3e7d68cd8 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -840,15 +840,10 @@ class XMLSrc(XMLFileBacked): def HandleEvent(self, _=None): """Read file upon update.""" - try: - data = open(self.name).read() - except IOError: - msg = "Failed to read file %s: %s" % (self.name, sys.exc_info()[1]) - self.logger.error(msg) - raise PluginExecutionError(msg) self.items = {} try: - xdata = lxml.etree.XML(data, parser=Bcfg2.Server.XMLParser) + xdata = lxml.etree.parse(self.name, + parser=Bcfg2.Server.XMLParser).getroot() except lxml.etree.XMLSyntaxError: msg = "Failed to parse file %s: %s" % (self.name, sys.exc_info()[1]) @@ -865,8 +860,6 @@ class XMLSrc(XMLFileBacked): self.logger.error(msg) raise PluginExecutionError(msg) - del xdata, data - def Cache(self, metadata): """Build a package dict for a given host.""" if self.cache is None or self.cache[0] != metadata: -- cgit v1.2.3-1-g7c22 From 9a4bc278b4cebf7dfe292951204e760ef70b828e Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Sun, 8 Dec 2013 21:21:50 -0500 Subject: bcfg2-lint: New TemplateAbuse plugin detects templated scripts TemplateAbuse detects templated scripts (either files that end with a known extension, or that start with a shebang line) and executables (based off of their permissions in info.xml) and warns about them, since templating scripts is dicey at best, and almost always better done by templating a config file for the script to read instead. --- src/lib/Bcfg2/Server/Lint/TemplateAbuse.py | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/lib/Bcfg2/Server/Lint/TemplateAbuse.py (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Lint/TemplateAbuse.py b/src/lib/Bcfg2/Server/Lint/TemplateAbuse.py new file mode 100644 index 000000000..fca9d14a9 --- /dev/null +++ b/src/lib/Bcfg2/Server/Lint/TemplateAbuse.py @@ -0,0 +1,75 @@ +""" Check for templated scripts or executables. """ + +import os +import stat +import Bcfg2.Server.Lint +from Bcfg2.Compat import any # pylint: disable=W0622 +from Bcfg2.Server.Plugin import DEFAULT_FILE_METADATA +from Bcfg2.Server.Plugins.Cfg.CfgInfoXML import CfgInfoXML +from Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator import CfgGenshiGenerator +from Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator import CfgCheetahGenerator +from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenshiGenerator import \ + CfgEncryptedGenshiGenerator +from Bcfg2.Server.Plugins.Cfg.CfgEncryptedCheetahGenerator import \ + CfgEncryptedCheetahGenerator + + +class TemplateAbuse(Bcfg2.Server.Lint.ServerPlugin): + """ Check for templated scripts or executables. """ + templates = [CfgGenshiGenerator, CfgCheetahGenerator, + CfgEncryptedGenshiGenerator, CfgEncryptedCheetahGenerator] + extensions = [".pl", ".py", ".sh", ".rb"] + + def Run(self): + if 'Cfg' in self.core.plugins: + for entryset in self.core.plugins['Cfg'].entries.values(): + for entry in entryset.entries.values(): + if (self.HandlesFile(entry.name) and + any(isinstance(entry, t) for t in self.templates)): + self.check_template(entryset, entry) + + @classmethod + def Errors(cls): + return {"templated-script": "warning", + "templated-executable": "warning"} + + def check_template(self, entryset, entry): + """ Check a template to see if it's a script or an executable. """ + # first, check for a known script extension + ext = os.path.splitext(entryset.path)[1] + if ext in self.extensions: + self.LintError("templated-script", + "Templated script found: %s\n" + "File has a known script extension: %s\n" + "Template a config file for the script instead" % + (entry.name, ext)) + return + + # next, check for a shebang line + firstline = open(entry.name).readline() + if firstline.startswith("#!"): + self.LintError("templated-script", + "Templated script found: %s\n" + "File starts with a shebang: %s\n" + "Template a config file for the script instead" % + (entry.name, firstline)) + return + + # finally, check for executable permissions in info.xml + for entry in entryset.entries.values(): + if isinstance(entry, CfgInfoXML): + for pinfo in entry.infoxml.pnode.data.xpath("//FileInfo"): + try: + mode = int(pinfo.get("mode", + DEFAULT_FILE_METADATA['mode']), 8) + except ValueError: + # LintError will be produced by RequiredAttrs plugin + self.logger.warning("Non-octal mode: %s" % mode) + continue + if mode & stat.S_IXUSR != 0: + self.LintError( + "templated-executable", + "Templated executable found: %s\n" + "Template a config file for the executable instead" + % entry.name) + return -- cgit v1.2.3-1-g7c22 From 14578f009d568850a918930180600807baa6c194 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Sun, 8 Dec 2013 21:57:04 -0500 Subject: bcfg2-lint: New ValidateJSON plugin This plugin validates the JSON files that can be used by Properties and Ohai. --- src/lib/Bcfg2/Server/Lint/ValidateJSON.py | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/lib/Bcfg2/Server/Lint/ValidateJSON.py (limited to 'src/lib/Bcfg2/Server') diff --git a/src/lib/Bcfg2/Server/Lint/ValidateJSON.py b/src/lib/Bcfg2/Server/Lint/ValidateJSON.py new file mode 100644 index 000000000..c4a82a5d2 --- /dev/null +++ b/src/lib/Bcfg2/Server/Lint/ValidateJSON.py @@ -0,0 +1,70 @@ +"""Ensure that all JSON files in the Bcfg2 repository are +valid. Currently, the only plugins that uses JSON are Ohai and +Properties.""" + +import os +import sys +import glob +import fnmatch +import Bcfg2.Server.Lint + +try: + import json +except ImportError: + import simplejson as json + + +class ValidateJSON(Bcfg2.Server.Lint.ServerlessPlugin): + """Ensure that all JSON files in the Bcfg2 repository are + valid. Currently, the only plugins that uses JSON are Ohai and + Properties. """ + + def __init__(self, *args, **kwargs): + Bcfg2.Server.Lint.ServerlessPlugin.__init__(self, *args, **kwargs) + + #: A list of file globs that give the path to JSON files. The + #: globs are extended :mod:`fnmatch` globs that also support + #: ``**``, which matches any number of any characters, + #: including forward slashes. + self.globs = ["Properties/*.json", "Ohai/*.json"] + self.files = self.get_files() + + def Run(self): + for path in self.files: + self.logger.debug("Validating JSON in %s" % path) + try: + json.load(open(path)) + except ValueError: + self.LintError("json-failed-to-parse", + "%s does not contain valid JSON: %s" % + (path, sys.exc_info()[1])) + + @classmethod + def Errors(cls): + return {"json-failed-to-parse": "error"} + + def get_files(self): + """Return a list of all JSON files to validate, based on + :attr:`Bcfg2.Server.Lint.ValidateJSON.ValidateJSON.globs`. """ + if self.files is not None: + listfiles = lambda p: fnmatch.filter(self.files, + os.path.join('*', p)) + else: + listfiles = lambda p: glob.glob(os.path.join(self.config['repo'], + p)) + + rv = [] + for path in self.globs: + if '/**/' in path: + if self.files is not None: + rv.extend(listfiles(path)) + else: # self.files is None + fpath, fname = path.split('/**/') + for root, _, files in \ + os.walk(os.path.join(self.config['repo'], + fpath)): + rv.extend([os.path.join(root, f) + for f in files if f == fname]) + else: + rv.extend(listfiles(path)) + return rv -- cgit v1.2.3-1-g7c22