summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/FileProbes.py
diff options
context:
space:
mode:
authorSol Jerome <sol.jerome@gmail.com>2012-03-24 11:20:07 -0500
committerSol Jerome <sol.jerome@gmail.com>2012-03-24 11:20:07 -0500
commitdab1d03d81c538966d03fb9318a4588a9e803b44 (patch)
treef51e27fa55887e9fb961766805fe43f0da56c5b9 /src/lib/Bcfg2/Server/Plugins/FileProbes.py
parent5cd6238df496a3cea178e4596ecd87967cce1ce6 (diff)
downloadbcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.tar.gz
bcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.tar.bz2
bcfg2-dab1d03d81c538966d03fb9318a4588a9e803b44.zip
Allow to run directly from a git checkout (#1037)
Signed-off-by: Sol Jerome <sol.jerome@gmail.com>
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/FileProbes.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/FileProbes.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/FileProbes.py b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
new file mode 100644
index 000000000..5beec7be0
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
@@ -0,0 +1,230 @@
+""" This module allows you to probe a client for a file, which is then
+added to the specification. On subsequent runs, the file will be
+replaced on the client if it is missing; if it has changed on the
+client, it can either be updated in the specification or replaced on
+the client """
+
+import os
+import sys
+import errno
+import binascii
+import lxml.etree
+import Bcfg2.Options
+import Bcfg2.Server.Plugin
+
+probecode = """#!/usr/bin/env python
+
+import os
+import pwd
+import grp
+import binascii
+import lxml.etree
+
+path = "%s"
+
+if not os.path.exists(path):
+ print "%%s does not exist" %% path
+ raise SystemExit(1)
+
+stat = os.stat(path)
+data = lxml.etree.Element("ProbedFileData",
+ name=path,
+ owner=pwd.getpwuid(stat[4])[0],
+ group=grp.getgrgid(stat[5])[0],
+ perms=oct(stat[0] & 07777))
+data.text = binascii.b2a_base64(open(path).read())
+print lxml.etree.tostring(data)
+"""
+
+class FileProbesConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked,
+ Bcfg2.Server.Plugin.StructFile):
+ """ Config file handler for FileProbes """
+ def __init__(self, filename, fam):
+ Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, filename, fam)
+ Bcfg2.Server.Plugin.StructFile.__init__(self, filename)
+
+
+class FileProbes(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Probing):
+ """ This module allows you to probe a client for a file, which is then
+ added to the specification. On subsequent runs, the file will be
+ replaced on the client if it is missing; if it has changed on the
+ client, it can either be updated in the specification or replaced on
+ the client """
+
+ name = 'FileProbes'
+ experimental = True
+ __author__ = 'chris.a.st.pierre@gmail.com'
+
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.Probing.__init__(self)
+ self.config = FileProbesConfig(os.path.join(self.data, 'config.xml'),
+ core.fam)
+ self.entries = dict()
+ self.probes = dict()
+
+ def GetProbes(self, metadata):
+ """Return a set of probes for execution on client."""
+ if metadata.hostname not in self.probes:
+ cfg = self.core.plugins['Cfg']
+ self.entries[metadata.hostname] = dict()
+ self.probes[metadata.hostname] = []
+ for entry in self.config.Match(metadata):
+ path = entry.get("name")
+ # do not probe for files that are already in Cfg and
+ # for which update is false; we can't possibly do
+ # anything with the data we get from such a probe
+ try:
+ if (entry.get('update', 'false').lower() == "false" and
+ cfg.entries[path].get_pertinent_entries(entry,
+ metadata)):
+ continue
+ except (KeyError, Bcfg2.Server.Plugin.PluginExecutionError):
+ pass
+ self.entries[metadata.hostname][path] = entry
+ probe = lxml.etree.Element('probe', name=path,
+ source=self.name,
+ interpreter="/usr/bin/env python")
+ probe.text = probecode % path
+ self.probes[metadata.hostname].append(probe)
+ self.debug_log("Adding file probe for %s to %s" %
+ (path, metadata.hostname))
+ return self.probes[metadata.hostname]
+
+ def ReceiveData(self, metadata, datalist):
+ """Receive data from probe."""
+ self.debug_log("Receiving file probe data from %s" % metadata.hostname)
+
+ for data in datalist:
+ if data.text is None:
+ self.logger.error("Got null response to %s file probe from %s" %
+ (data.get('name'), metadata.hostname))
+ else:
+ try:
+ self.write_data(lxml.etree.XML(data.text), metadata)
+ except lxml.etree.XMLSyntaxError:
+ # if we didn't get XML back from the probe, assume
+ # it's an error message
+ self.logger.error(data.text)
+
+ def write_data(self, data, metadata):
+ """Write the probed file data to the bcfg2 specification."""
+ filename = data.get("name")
+ contents = binascii.a2b_base64(data.text)
+ entry = self.entries[metadata.hostname][filename]
+ cfg = self.core.plugins['Cfg']
+ specific = "%s.H_%s" % (os.path.basename(filename), metadata.hostname)
+ # we can't use os.path.join() for this because specific
+ # already has a leading /, which confuses os.path.join()
+ fileloc = "%s%s" % (cfg.data, os.path.join(filename, specific))
+
+ create = False
+ try:
+ cfg.entries[filename].bind_entry(entry, metadata)
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ create = True
+
+ # get current entry data
+ if entry.text and entry.get("encoding") == "base64":
+ entrydata = binascii.a2b_base64(entry.text)
+ else:
+ entrydata = entry.text
+
+ if create:
+ self.logger.info("Writing new probed file %s" % fileloc)
+ self.write_file(fileloc, contents)
+ self.verify_file(filename, contents, metadata)
+ infoxml = os.path.join("%s%s" % (cfg.data, filename), "info.xml")
+ self.write_infoxml(infoxml, entry, data)
+ elif entrydata == contents:
+ self.debug_log("Existing %s contents match probed contents" %
+ filename)
+ return
+ elif (entry.get('update', 'false').lower() == "true"):
+ self.logger.info("Writing updated probed file %s" % fileloc)
+ self.write_file(fileloc, contents)
+ self.verify_file(filename, contents, metadata)
+ else:
+ self.logger.info("Skipping updated probed file %s" % fileloc)
+ return
+
+ def write_file(self, fileloc, contents):
+ try:
+ os.makedirs(os.path.dirname(fileloc))
+ except OSError:
+ err = sys.exc_info()[1]
+ if err.errno == errno.EEXIST:
+ pass
+ else:
+ self.logger.error("Could not create parent directories for %s: "
+ "%s" % (fileloc, err))
+ return
+
+ try:
+ open(fileloc, 'wb').write(contents)
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Could not write %s: %s" % (fileloc, err))
+ return
+
+ def verify_file(self, filename, contents, metadata):
+ # Service the FAM events queued up by the key generation so
+ # the data structure entries will be available for binding.
+ #
+ # NOTE: We wait for up to ten seconds. There is some potential
+ # for race condition, because if the file monitor doesn't get
+ # notified about the new key files in time, those entries
+ # won't be available for binding. In practice, this seems
+ # "good enough".
+ entry = self.entries[metadata.hostname][filename]
+ cfg = self.core.plugins['Cfg']
+ tries = 0
+ updated = False
+ while not updated:
+ if tries >= 10:
+ self.logger.error("%s still not registered" % filename)
+ return
+ self.core.fam.handle_events_in_interval(1)
+ try:
+ cfg.entries[filename].bind_entry(entry, metadata)
+ except Bcfg2.Server.Plugin.PluginExecutionError:
+ tries += 1
+ continue
+
+ # get current entry data
+ if entry.get("encoding") == "base64":
+ entrydata = binascii.a2b_base64(entry.text)
+ else:
+ entrydata = entry.text
+ if entrydata == contents:
+ updated = True
+ tries += 1
+
+ def write_infoxml(self, infoxml, entry, data):
+ """ write an info.xml for the file """
+ if os.path.exists(infoxml):
+ return
+
+ self.logger.info("Writing info.xml at %s for %s" %
+ (infoxml, data.get("name")))
+ info = \
+ lxml.etree.Element("Info",
+ owner=data.get("owner",
+ Bcfg2.Options.MDATA_OWNER.value),
+ group=data.get("group",
+ Bcfg2.Options.MDATA_GROUP.value),
+ perms=data.get("perms",
+ Bcfg2.Options.MDATA_PERMS.value),
+ encoding=entry.get("encoding",
+ Bcfg2.Options.ENCODING.value))
+
+ root = lxml.etree.Element("FileInfo")
+ root.append(info)
+ try:
+ open(infoxml, "w").write(lxml.etree.tostring(root,
+ pretty_print=True))
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Could not write %s: %s" % (fileloc, err))
+ return