summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/Cfg
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-12-20 10:02:41 -0600
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-12-20 10:02:41 -0600
commit10326a34dd813b88c6c8816115e91977a93a1f10 (patch)
tree149a2a54ce77bfcf70e9268561c1d9a701c9c0d1 /src/lib/Bcfg2/Server/Plugins/Cfg
parentd50d6a4625967113afbd50c658cfc89547c06ffc (diff)
downloadbcfg2-10326a34dd813b88c6c8816115e91977a93a1f10.tar.gz
bcfg2-10326a34dd813b88c6c8816115e91977a93a1f10.tar.bz2
bcfg2-10326a34dd813b88c6c8816115e91977a93a1f10.zip
Cfg: added creator handler to perform one-time creation of static data
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/Cfg')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py7
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py209
3 files changed, 159 insertions, 59 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
index 73550cd9d..7cce2b6f8 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py
@@ -101,7 +101,7 @@ class CfgGenshiGenerator(CfgGenerator):
__init__.__doc__ = CfgGenerator.__init__.__doc__
def get_data(self, entry, metadata):
- fname = entry.get('realname', entry.get('name'))
+ fname = entry.get('name')
stream = \
self.template.generate(name=fname,
metadata=metadata,
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
index 7277d5d08..5122d9aa1 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgLegacyInfo.py
@@ -1,11 +1,8 @@
""" Handle info and :info files """
-import logging
import Bcfg2.Server.Plugin
from Bcfg2.Server.Plugins.Cfg import CfgInfo
-LOGGER = logging.getLogger(__name__)
-
class CfgLegacyInfo(CfgInfo):
""" CfgLegacyInfo handles :file:`info` and :file:`:info` files for
@@ -37,8 +34,8 @@ class CfgLegacyInfo(CfgInfo):
for line in open(self.path).readlines():
match = Bcfg2.Server.Plugin.INFO_REGEX.match(line)
if not match:
- LOGGER.warning("Failed to parse line in %s: %s" %
- (event.filename, line))
+ self.logger.warning("Failed to parse line in %s: %s" %
+ (event.filename, line))
continue
else:
for key, value in list(match.groupdict().items()):
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index c8859dad3..2466d68a2 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -4,19 +4,18 @@ import re
import os
import sys
import stat
-import logging
+import errno
import operator
import lxml.etree
import Bcfg2.Options
import Bcfg2.Server.Plugin
import Bcfg2.Server.Lint
+from Bcfg2.Server.Plugin import PluginExecutionError
# pylint: disable=W0622
-from Bcfg2.Compat import u_str, unicode, b64encode, walk_packages, any, \
- oct_mode
+from Bcfg2.Compat import u_str, unicode, b64encode, b64decode, walk_packages, \
+ any, oct_mode
# pylint: enable=W0622
-LOGGER = logging.getLogger(__name__)
-
#: SETUP contains a reference to the
#: :class:`Bcfg2.Options.OptionParser` created by the Bcfg2 core for
#: parsing command-line and config file options.
@@ -29,7 +28,8 @@ LOGGER = logging.getLogger(__name__)
SETUP = None
-class CfgBaseFileMatcher(Bcfg2.Server.Plugin.SpecificData):
+class CfgBaseFileMatcher(Bcfg2.Server.Plugin.SpecificData,
+ Bcfg2.Server.Plugin.Debuggable):
""" .. currentmodule:: Bcfg2.Server.Plugins.Cfg
CfgBaseFileMatcher is the parent class for all Cfg handler
@@ -70,9 +70,13 @@ class CfgBaseFileMatcher(Bcfg2.Server.Plugin.SpecificData):
#: Flag to indicate a deprecated handler.
deprecated = False
+ #: Flag to indicate an experimental handler.
+ experimental = False
+
def __init__(self, name, specific, encoding):
Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific,
encoding)
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
self.encoding = encoding
__init__.__doc__ = Bcfg2.Server.Plugin.SpecificData.__init__.__doc__ + \
"""
@@ -183,7 +187,6 @@ class CfgGenerator(CfgBaseFileMatcher):
:param metadata: The client metadata to generate data for.
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: string - the contents of the entry
- :raises: any
"""
return self.data
@@ -290,11 +293,74 @@ class CfgVerifier(CfgBaseFileMatcher):
:param data: The contents of the entry
:type data: string
:returns: None
- :raises: Bcfg2.Server.Plugins.Cfg.CfgVerificationError
+ :raises: :exc:`Bcfg2.Server.Plugins.Cfg.CfgVerificationError`
"""
raise NotImplementedError
+class CfgCreator(CfgBaseFileMatcher):
+ """ CfgCreator handlers create static entry data if no generator
+ was found to generate any. A CfgCreator runs at most once per
+ client, writes its data to disk as a static file, and is not
+ called on subsequent runs by the same client. """
+
+ def create_data(self, entry, metadata):
+ """ Create new data for the given entry and write it to disk
+ using :func:`Bcfg2.Server.Plugins.Cfg.CfgCreator.write_data`.
+
+ :param entry: The entry to create data for.
+ :type entry: lxml.etree._Element
+ :param metadata: The client metadata to create data for.
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: string - the contents of the entry
+ """
+ raise NotImplementedError
+
+ def write_data(self, data, host=None, group=None, prio=0):
+ """ Write the new data to disk. If ``host`` is given, it is
+ written as a host-specific file, or as a group-specific file
+ if ``group`` and ``prio`` are given. If neither ``host`` nor
+ ``group`` is given, it will be written as a non-specific file.
+
+ :param data: The data to write
+ :type data: string
+ :param host: The data applies to the given host
+ :type host: bool
+ :param group: The data applies to the given group
+ :type group: string
+ :param prio: The data has the given priority relative to other
+ objects that also apply to the same group.
+ ``group`` must also be specified.
+ :type prio: int
+ :returns: None
+ :raises: :exc:`Bcfg2.Server.Plugins.Cfg.CfgCreationError`
+ """
+ basefilename = \
+ os.path.join(os.path.dirname(self.name),
+ os.path.basename(os.path.dirname(self.name)))
+ if group:
+ fileloc = "%s.G%02d_%s" % (basefilename, prio, group)
+ elif host:
+ fileloc = "%s.H_%s" % (basefilename, host)
+ else:
+ fileloc = basefilename
+
+ self.debug_log("%s: Writing new file %s" % (self.name, fileloc))
+ try:
+ os.makedirs(os.path.dirname(fileloc))
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno != errno.EEXIST:
+ raise CfgCreationError("Could not create parent directories "
+ "for %s: %s" % (fileloc, err))
+
+ try:
+ open(fileloc, 'wb').write(data)
+ except IOError:
+ err = sys.exc_info()[1]
+ raise CfgCreationError("Could not write %s: %s" % (fileloc, err))
+
+
class CfgVerificationError(Exception):
""" Raised by
:func:`Bcfg2.Server.Plugins.Cfg.CfgVerifier.verify_entry` when an
@@ -302,6 +368,12 @@ class CfgVerificationError(Exception):
pass
+class CfgCreationError(Exception):
+ """ Raised by :class:`Bcfg2.Server.Plugins.Cfg.CfgCreator` when
+ various stages of data creation fail """
+ pass
+
+
class CfgDefaultInfo(CfgInfo):
""" :class:`Bcfg2.Server.Plugins.Cfg.Cfg` handler that supplies a
default set of file metadata """
@@ -323,13 +395,15 @@ class CfgDefaultInfo(CfgInfo):
DEFAULT_INFO = CfgDefaultInfo(Bcfg2.Server.Plugin.DEFAULT_FILE_METADATA)
-class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
+class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet,
+ Bcfg2.Server.Plugin.Debuggable):
""" Handle a collection of host- and group-specific Cfg files with
multiple different Cfg handlers in a single directory. """
def __init__(self, basename, path, entry_type, encoding):
Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path,
entry_type, encoding)
+ Bcfg2.Server.Plugin.Debuggable.__init__(self)
self.specific = None
self._handlers = None
__init__.__doc__ = Bcfg2.Server.Plugin.EntrySet.__doc__
@@ -347,8 +421,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
module = getattr(__import__(submodule[1]).Server.Plugins.Cfg,
mname)
hdlr = getattr(module, mname)
- if set(hdlr.__mro__).intersection([CfgInfo, CfgFilter,
- CfgGenerator, CfgVerifier]):
+ if CfgBaseFileMatcher in hdlr.__mro__:
self._handlers.append(hdlr)
self._handlers.sort(key=operator.attrgetter("__priority__"))
return self._handlers
@@ -374,16 +447,16 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
if action == 'changed':
# warn about a bogus 'changed' event, but
# handle it like a 'created'
- LOGGER.warning("Got %s event for unknown file %s" %
- (action, event.filename))
+ self.logger.warning("Got %s event for unknown file %s"
+ % (action, event.filename))
self.debug_log("%s handling %s event on %s" %
(hdlr.__name__, action, event.filename))
try:
self.entry_init(event, hdlr)
except: # pylint: disable=W0702
err = sys.exc_info()[1]
- LOGGER.error("Cfg: Failed to parse %s: %s" %
- (event.filename, err))
+ self.logger.error("Cfg: Failed to parse %s: %s" %
+ (event.filename, err))
return
elif hdlr.ignore(event, basename=self.path):
return
@@ -394,8 +467,8 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
del self.entries[event.filename]
return
- LOGGER.error("Could not process event %s for %s; ignoring" %
- (action, event.filename))
+ self.logger.error("Could not process event %s for %s; ignoring" %
+ (action, event.filename))
def get_matching(self, metadata):
return self.get_handlers(metadata, CfgGenerator)
@@ -421,7 +494,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
specific=hdlr.get_regex([os.path.basename(self.path)]))
else:
if event.filename in self.entries:
- LOGGER.warn("Got duplicate add for %s" % event.filename)
+ self.logger.warn("Got duplicate add for %s" % event.filename)
else:
fpath = os.path.join(self.path, event.filename)
self.entries[event.filename] = hdlr(fpath)
@@ -441,8 +514,8 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
msg = "Data for %s for %s failed to verify: %s" % \
(entry.get('name'), metadata.hostname,
sys.exc_info()[1])
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
if entry.get('encoding') == 'base64':
data = b64encode(data)
@@ -453,16 +526,17 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
except UnicodeDecodeError:
msg = "Failed to decode %s: %s" % (entry.get('name'),
sys.exc_info()[1])
- LOGGER.error(msg)
- LOGGER.error("Please verify you are using the proper encoding")
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ self.logger.error("Please verify you are using the proper "
+ "encoding")
+ raise PluginExecutionError(msg)
except ValueError:
msg = "Error in specification for %s: %s" % (entry.get('name'),
sys.exc_info()[1])
- LOGGER.error(msg)
- LOGGER.error("You need to specify base64 encoding for %s" %
- entry.get('name'))
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ self.logger.error("You need to specify base64 encoding for %s"
+ % entry.get('name'))
+ raise PluginExecutionError(msg)
except TypeError:
# data is already unicode; newer versions of Cheetah
# seem to return unicode
@@ -489,13 +563,17 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
if (isinstance(ent, handler_type) and
(not ent.__specific__ or ent.specific.matches(metadata))):
rv.append(ent)
+
+ if ent.__basenames__:
+ fdesc = "/".join(ent.__basenames__)
+ elif ent.__extensions__:
+ fdesc = "." + "/.".join(ent.__extensions__)
if ent.deprecated:
- if ent.__basenames__:
- fdesc = "/".join(ent.__basenames__)
- elif ent.__extensions__:
- fdesc = "." + "/.".join(ent.__extensions__)
- LOGGER.warning("Cfg: %s: Use of %s files is deprecated" %
- (ent.name, fdesc))
+ self.logger.warning("Cfg: %s: Use of %s files is "
+ "deprecated" % (ent.name, fdesc))
+ elif ent.experimental:
+ self.logger.warning("Cfg: %s: Use of %s files is "
+ "experimental" % (ent.name, fdesc))
return rv
def bind_info_to_entry(self, entry, metadata):
@@ -512,30 +590,55 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
info_handlers = self.get_handlers(metadata, CfgInfo)
DEFAULT_INFO.bind_info_to_entry(entry, metadata)
if len(info_handlers) > 1:
- LOGGER.error("More than one info supplier found for %s: %s" %
- (entry.get("name"), info_handlers))
+ self.logger.error("More than one info supplier found for %s: %s" %
+ (entry.get("name"), info_handlers))
if len(info_handlers):
info_handlers[0].bind_info_to_entry(entry, metadata)
if entry.tag == 'Path':
entry.set('type', 'file')
+ def _create_data(self, entry, metadata):
+ """ Create data for the given entry on the given client
+
+ :param entry: The abstract entry to create data for. This
+ will not be modified
+ :type entry: lxml.etree._Element
+ :param metadata: The client metadata to create data for
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: string - the data for the entry
+ """
+ creator = self.best_matching(metadata, self.get_handlers(metadata,
+ CfgCreator))
+
+ try:
+ return creator.create_data(entry, metadata)
+ except:
+ raise PluginExecutionError("Cfg: Error creating data for %s: %s" %
+ (entry.get("name"), sys.exc_info()[1]))
+
def _generate_data(self, entry, metadata):
""" Generate data for the given entry on the given client
:param entry: The abstract entry to generate data for. This
will not be modified
:type entry: lxml.etree._Element
- :param metadata: The client metadata generate data for
+ :param metadata: The client metadata to generate data for
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: string - the data for the entry
"""
- generator = self.best_matching(metadata,
- self.get_handlers(metadata,
- CfgGenerator))
+ try:
+ generator = self.best_matching(metadata,
+ self.get_handlers(metadata,
+ CfgGenerator))
+ except PluginExecutionError:
+ # if no creators or generators exist, _create_data()
+ # raises an appropriate exception
+ return self._create_data(entry, metadata)
+
if entry.get('mode').lower() == 'inherit':
# use on-disk permissions
- LOGGER.warning("Cfg: %s: Use of mode='inherit' is deprecated" %
- entry.get("name"))
+ self.logger.warning("Cfg: %s: Use of mode='inherit' is deprecated"
+ % entry.get("name"))
fname = os.path.join(self.path, generator.name)
entry.set('mode',
oct_mode(stat.S_IMODE(os.stat(fname).st_mode)))
@@ -544,8 +647,8 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
except:
msg = "Cfg: Error rendering %s: %s" % (entry.get("name"),
sys.exc_info()[1])
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
def _validate_data(self, entry, metadata, data):
""" Validate data for the given entry on the given client
@@ -578,8 +681,8 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
ent.specific.matches(metadata))]
if not generators:
msg = "No base file found for %s" % entry.get('name')
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
rv = []
try:
@@ -609,19 +712,19 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
name = self.build_filename(specific)
if os.path.exists("%s.genshi" % name):
msg = "Cfg: Unable to pull data for genshi types"
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
elif os.path.exists("%s.cheetah" % name):
msg = "Cfg: Unable to pull data for cheetah types"
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
try:
etext = new_entry['text'].encode(self.encoding)
except:
msg = "Cfg: Cannot encode content of %s as %s" % \
(name, self.encoding)
- LOGGER.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
open(name, 'w').write(etext)
self.debug_log("Wrote file %s" % name, flag=log)
badattr = [attr for attr in ['owner', 'group', 'mode']
@@ -631,8 +734,8 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
for ifile in ['info', ':info']:
info = os.path.join(self.path, ifile)
if os.path.exists(info):
- LOGGER.info("Removing %s and replacing with info.xml" %
- info)
+ self.logger.info("Removing %s and replacing with info.xml"
+ % info)
os.remove(info)
metadata_updates = {}
metadata_updates.update(self.metadata)