summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Properties.py
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-15 10:27:47 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-15 11:28:05 -0400
commit32536152850a683e18935eb5223a5bd1410e9258 (patch)
tree3c12e9f72bee87b5c84c818362fe595178688acc /src/lib/Bcfg2/Server/Plugins/Properties.py
parent96108cfae8b68d6265e4643ea9519bdfd9127752 (diff)
downloadbcfg2-32536152850a683e18935eb5223a5bd1410e9258.tar.gz
bcfg2-32536152850a683e18935eb5223a5bd1410e9258.tar.bz2
bcfg2-32536152850a683e18935eb5223a5bd1410e9258.zip
added support for JSON and YAML properties files
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/Properties.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py229
1 files changed, 206 insertions, 23 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index d9e622645..aef5238c6 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -16,17 +16,42 @@ try:
except ImportError:
HAS_CRYPTO = False
+try:
+ import json
+ HAS_JSON = True
+except ImportError:
+ try:
+ import simplejson as json
+ HAS_JSON = True
+ except ImportError:
+ HAS_JSON = False
+
+try:
+ import yaml
+ HAS_YAML = True
+except ImportError:
+ HAS_YAML = False
+
LOGGER = logging.getLogger(__name__)
SETUP = None
-class PropertyFile(Bcfg2.Server.Plugin.StructFile):
- """ Class for properties files. """
+class PropertyFile(object):
+ """ Base Properties file handler """
+
+ def __init__(self, name):
+ """
+ :param name: The filename of this properties file.
+
+ .. automethod:: _write
+ """
+ self.name = name
def write(self):
""" Write the data in this data structure back to the property
- file """
+ file. This public method performs checking to ensure that
+ writing is possible and then calls :func:`_write`. """
if not SETUP.cfp.getboolean("properties", "writes_enabled",
default=True):
msg = "Properties files write-back is disabled in the " + \
@@ -39,19 +64,123 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
msg = "Cannot write %s: %s" % (self.name, sys.exc_info()[1])
LOGGER.error(msg)
raise PluginExecutionError(msg)
-
try:
- open(self.name, "wb").write(
- lxml.etree.tostring(self.xdata,
- xml_declaration=False,
- pretty_print=True).decode('UTF-8'))
- return True
+ return self._write()
except IOError:
err = sys.exc_info()[1]
msg = "Failed to write %s: %s" % (self.name, err)
LOGGER.error(msg)
raise PluginExecutionError(msg)
+ def _write(self):
+ """ Write the data in this data structure back to the property
+ file. """
+ raise NotImplementedError
+
+ def validate_data(self):
+ """ Verify that the data in this file is valid. """
+ raise NotImplementedError
+
+ def get_additional_data(self, metadata): # pylint: disable=W0613
+ """ Get file data for inclusion in client metadata. """
+ return copy.copy(self)
+
+
+class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
+ """ Handle JSON Properties files. """
+
+ def __init__(self, name, fam=None):
+ Bcfg2.Server.Plugin.FileBacked.__init__(self, name, fam=fam)
+ PropertyFile.__init__(self, name)
+ self.json = None
+ __init__.__doc__ = Bcfg2.Server.Plugin.FileBacked.__init__.__doc__
+
+ def Index(self):
+ try:
+ self.json = json.loads(self.data)
+ except ValueError:
+ err = sys.exc_info()[1]
+ raise PluginExecutionError("Could not load JSON data from %s: %s" %
+ (self.name, err))
+ Index.__doc__ = Bcfg2.Server.Plugin.FileBacked.Index.__doc__
+
+ def _write(self):
+ json.dump(self.json, open(self.name, 'wb'))
+ return True
+ _write.__doc__ = PropertyFile._write.__doc__
+
+ def validate_data(self):
+ try:
+ json.dumps(self.json)
+ except:
+ err = sys.exc_info()[1]
+ raise PluginExecutionError("Data for %s cannot be dumped to JSON: "
+ "%s" % (self.name, err))
+ validate_data.__doc__ = PropertyFile.validate_data.__doc__
+
+ def __str__(self):
+ return str(self.json)
+
+ def __repr__(self):
+ return repr(self.json)
+
+
+class YAMLPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile):
+ """ Handle YAML Properties files. """
+
+ def __init__(self, name, fam=None):
+ Bcfg2.Server.Plugin.FileBacked.__init__(self, name, fam=fam)
+ PropertyFile.__init__(self, name)
+ self.yaml = None
+ __init__.__doc__ = Bcfg2.Server.Plugin.FileBacked.__init__.__doc__
+
+ def Index(self):
+ try:
+ self.yaml = yaml.load(self.data)
+ except yaml.YAMLError:
+ err = sys.exc_info()[1]
+ raise PluginExecutionError("Could not load YAML data from %s: %s" %
+ (self.name, err))
+ Index.__doc__ = Bcfg2.Server.Plugin.FileBacked.Index.__doc__
+
+ def _write(self):
+ yaml.dump(self.yaml, open(self.name, 'wb'))
+ return True
+ _write.__doc__ = PropertyFile._write.__doc__
+
+ def validate_data(self):
+ try:
+ yaml.dump(self.yaml)
+ except yaml.YAMLError:
+ err = sys.exc_info()[1]
+ raise PluginExecutionError("Data for %s cannot be dumped to YAML: "
+ "%s" % (self.name, err))
+ validate_data.__doc__ = PropertyFile.validate_data.__doc__
+
+ def __str__(self):
+ return str(self.yaml)
+
+ def __repr__(self):
+ return repr(self.yaml)
+
+
+class XMLPropertyFile(Bcfg2.Server.Plugin.StructFile, PropertyFile):
+ """ Handle XML Properties files. """
+
+ def __init__(self, name, fam=None, should_monitor=False):
+ Bcfg2.Server.Plugin.StructFile.__init__(self, name, fam=fam,
+ should_monitor=should_monitor)
+ PropertyFile.__init__(self, name)
+ __init__.__doc__ = Bcfg2.Server.Plugin.StructFile.__init__.__doc__
+
+ def _write(self):
+ open(self.name, "wb").write(
+ lxml.etree.tostring(self.xdata,
+ xml_declaration=False,
+ pretty_print=True).decode('UTF-8'))
+ return True
+ _write.__doc__ = PropertyFile._write.__doc__
+
def validate_data(self):
""" ensure that the data in this object validates against the
XML schema for this property file (if a schema exists) """
@@ -73,6 +202,7 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
self.name)
else:
return True
+ validate_data.__doc__ = PropertyFile.validate_data.__doc__
def Index(self):
Bcfg2.Server.Plugin.StructFile.Index(self)
@@ -89,6 +219,7 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
self.name)
LOGGER.error(msg)
raise PluginExecutionError(msg)
+ Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__
def _decrypt(self, element):
""" Decrypt a single encrypted properties file element """
@@ -111,19 +242,77 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
algorithm=get_algorithm(SETUP))
raise EVPError("Failed to decrypt")
+ def get_additional_data(self, metadata):
+ if SETUP.cfp.getboolean("properties", "automatch", default=False):
+ default_automatch = "true"
+ else:
+ default_automatch = "false"
+
+ if self.xdata.get("automatch", default_automatch).lower() == "true":
+ return self.XMLMatch(metadata)
+ else:
+ return copy.copy(self)
+
+ def __str__(self):
+ return str(self.xdata)
+
+ def __repr__(self):
+ return repr(self.xdata)
+
class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
- """ A collection of properties files """
- __child__ = PropertyFile
- patterns = re.compile(r'.*\.xml$')
+ """ A collection of properties files. """
+ extensions = ["xml"]
+ if HAS_JSON:
+ extensions.append("json")
+ if HAS_YAML:
+ extensions.extend(["yaml", "yml"])
+
+ #: Only track and include files whose names and paths match this
+ #: regex. Created on-the-fly based on which libraries are
+ #: installed (and thus which data formats are supported).
+ #: Candidates are ``.xml`` (always supported), ``.json``,
+ #: ``.yaml``, and ``.yml``.
+ patterns = re.compile(r'.*\.%s$' % '|'.join(extensions))
+
+ #: Ignore XML schema (``.xsd``) files
ignore = re.compile(r'.*\.xsd$')
+ def __init__(self, data, fam):
+ Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, data, fam)
+
+ #: Instead of creating children of this object with a static
+ #: object, we use :func:`property_dispatcher` to create a
+ #: child of the appropriate subclass of :class:`PropertyFile`
+ self.__child__ = self.property_dispatcher
+ __init__.__doc__ = Bcfg2.Server.Plugin.DirectoryBacked.__init__.__doc__
+
+ def property_dispatcher(self, fname, fam):
+ """ Dispatch an event on a Properties file to the
+ appropriate object.
+
+ :param fname: The name of the file that received the event
+ :type fname: string
+ :param fam: The file monitor the event was received by
+ :type fam: Bcfg2.Server.FileMonitor.FileMonitor
+ :returns: An object of the appropriate subclass of
+ :class:`PropertyFile`
+ """
+ if fname.endswith(".xml"):
+ return XMLPropertyFile(fname, fam)
+ elif HAS_JSON and fname.endswith(".json"):
+ return JSONPropertyFile(fname, fam)
+ elif HAS_YAML and (fname.endswith(".yaml") or fname.endswith(".yml")):
+ return YAMLPropertyFile(fname, fam)
+ else:
+ raise Bcfg2.Server.Plugin.PluginExecutionError(
+ "Properties: Unknown extension %s" % fname)
+
class Properties(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.Connector):
""" The properties plugin maps property files into client metadata
instances. """
- name = 'Properties'
def __init__(self, core, datastore):
global SETUP # pylint: disable=W0603
@@ -138,18 +327,12 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
raise Bcfg2.Server.Plugin.PluginInitError
SETUP = core.setup
+ __init__.__doc__ = Bcfg2.Server.Plugin.Plugin.__init__.__doc__
def get_additional_data(self, metadata):
- if self.core.setup.cfp.getboolean("properties", "automatch",
- default=False):
- default_automatch = "true"
- else:
- default_automatch = "false"
rv = dict()
for fname, pfile in self.store.entries.items():
- if pfile.xdata.get("automatch",
- default_automatch).lower() == "true":
- rv[fname] = pfile.XMLMatch(metadata)
- else:
- rv[fname] = copy.copy(pfile)
+ rv[fname] = pfile.get_additional_data(metadata)
return rv
+ get_additional_data.__doc__ = \
+ Bcfg2.Server.Plugin.Connector.get_additional_data.__doc__