summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Plugins/Cfg.py
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2005-09-07 17:35:15 +0000
committerNarayan Desai <desai@mcs.anl.gov>2005-09-07 17:35:15 +0000
commit818863a8bad4764c67837bc718697d936746a264 (patch)
treee159fcbf162678007a774ef355b3fbdefee7ff57 /src/lib/Server/Plugins/Cfg.py
parent6f728506855962f67fdc14ececd1fbefd0281f10 (diff)
downloadbcfg2-818863a8bad4764c67837bc718697d936746a264.tar.gz
bcfg2-818863a8bad4764c67837bc718697d936746a264.tar.bz2
bcfg2-818863a8bad4764c67837bc718697d936746a264.zip
update to new Plugin API
2005/09/06 22:35:15-05:00 anl.gov!desai Rename: src/lib/Server/Generators/Cfg.py -> src/lib/Server/Plugins/Cfg.py (Logical change 1.300) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1202 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src/lib/Server/Plugins/Cfg.py')
-rw-r--r--src/lib/Server/Plugins/Cfg.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py
index e69de29bb..85ad41d94 100644
--- a/src/lib/Server/Plugins/Cfg.py
+++ b/src/lib/Server/Plugins/Cfg.py
@@ -0,0 +1,278 @@
+'''This module implements a config file repository'''
+__revision__ = '$Revision$'
+
+from binascii import b2a_base64
+from os import stat
+from re import compile as regcompile
+from stat import S_ISDIR, ST_MODE
+from syslog import syslog, LOG_INFO, LOG_ERR
+
+from Bcfg2.Server.Plugin import Plugin, PluginExecutionError, FileBacked
+
+class FileEntry(FileBacked):
+ '''The File Entry class pertains to the config files contained in a particular directory.
+ This includes :info, all base files and deltas'''
+
+ def __init__(self, name, all, image, classes, bundles, attribs, hostname):
+ FileBacked.__init__(self, name)
+ self.all = all
+ self.image = image
+ self.bundles = bundles
+ self.classes = classes
+ self.attributes = attribs
+ self.hostname = hostname
+
+ def __cmp__(self, other):
+ fields = ['all', 'image', 'classes', 'bundles', 'attributes', 'hostname']
+ try:
+ most1 = [index for index in range(len(fields)) if getattr(self, fields[index])][0]
+ except IndexError:
+ most1 = 0
+ try:
+ most2 = [index for index in range(len(fields)) if getattr(other, fields[index])][0]
+ except IndexError:
+ most2 = 0
+ if most1 == most2:
+ if self.name.split('.')[-1] in ['cat', 'diff']:
+ meta1 = self.name.split('.')[-2]
+ else:
+ meta1 = self.name.split('.')[-1]
+ if other.name.split('.')[-1] in ['cat', 'diff']:
+ meta2 = other.name.split('.')[-2]
+ else:
+ meta2 = other.name.split('.')[-1]
+
+ if meta1[0] not in ['C', 'B']:
+ return 0
+ # need to tiebreak with numeric prio
+ prio1 = int(meta1[1:3])
+ prio2 = int(meta2[1:3])
+ return prio1 - prio2
+ else:
+ return most1 - most2
+
+class ConfigFileEntry(object):
+ '''ConfigFileEntry is a repository entry for a single file, containing
+ all data for all clients.'''
+ specific = regcompile('(.*/)(?P<filename>[\S\-.]+)\.((H_(?P<hostname>\S+))|' +
+ '(B(?P<bprio>\d+)_(?P<bundle>\S+))|(A(?P<aprio>\d+)_(?P<attr>\S+))|' +
+ '(I_(?P<image>\S+))|(C(?P<cprio>\d+)_(?P<class>\S+)))$')
+ info = regcompile('^owner:(\s)*(?P<owner>\w+)|group:(\s)*(?P<group>\w+)|' +
+ 'perms:(\s)*(?P<perms>\w+)|encoding:(\s)*(?P<encoding>\w+)|' +
+ '(?P<paranoid>paranoid(\s)*)$')
+
+ def __init__(self, path, repopath):
+ object.__init__(self)
+ self.path = path
+ self.repopath = repopath
+ self.basefiles = []
+ self.deltas = []
+ self.metadata = {'encoding': 'ascii', 'owner':'root', 'group':'root', 'perms':'0644'}
+ self.paranoid = False
+
+ def read_info(self):
+ '''read in :info metadata'''
+ filename = "%s/:info" % self.repopath
+ for line in open(filename).readlines():
+ match = self.info.match(line)
+ if not match:
+ continue
+ else:
+ mgd = match.groupdict()
+ if mgd['owner']:
+ self.metadata['owner'] = mgd['owner']
+ elif mgd['group']:
+ self.metadata['group'] = mgd['group']
+ elif mgd['encoding']:
+ self.metadata['encoding'] = mgd['encoding']
+ elif mgd['perms']:
+ self.metadata['perms'] = mgd['perms']
+ if len(self.metadata['perms']) == 3:
+ self.metadata['perms'] = "0%s" % (self.metadata['perms'])
+ elif mgd['paranoid']:
+ self.paranoid = True
+
+ def AddEntry(self, name):
+ '''add new file additions for a single cf file'''
+ delta = False
+ oldname = name
+ if name[-5:] == ':info':
+ return self.read_info()
+
+ if name.split('/')[-1] == self.path.split('/')[-1]:
+ self.basefiles.append(FileEntry(name, True, None, [], [], [], None))
+ self.basefiles.sort()
+ return
+
+ if name.split('/')[-1].split('.')[-1] in ['cat']:
+ delta = True
+ oldname = name
+ name = name[:-4]
+
+ specmatch = self.specific.match(name)
+ if specmatch == None:
+ syslog(LOG_ERR, "Cfg: Failed to match file %s" % (name))
+ return
+
+ data = {}
+ for item, value in specmatch.groupdict().iteritems():
+ if value != None:
+ data[item] = value
+
+ cfile = FileEntry(oldname, False, data.get('image', None), data.get('class', []),
+ data.get('bundle', []), data.get('attr', []), data.get('hostname', None))
+
+ if delta:
+ self.deltas.append(cfile)
+ self.deltas.sort()
+ else:
+ self.basefiles.append(cfile)
+ self.basefiles.sort()
+
+ def HandleEvent(self, event):
+ '''Handle FAM updates'''
+ action = event.code2str()
+ if event.filename == ':info':
+ if action in ['changed', 'exists', 'created']:
+ return self.read_info()
+ if event.filename != self.path.split('/')[-1]:
+ if not self.specific.match('/' + event.filename):
+ syslog(LOG_INFO, 'Cfg: Suppressing event for bogus file %s' % event.filename)
+ return
+
+ entries = [entry for entry in self.basefiles + self.deltas if
+ entry.name.split('/')[-1] == event.filename]
+
+ if len(entries) == 0:
+ syslog(LOG_ERR, "Cfg: Failed to match entry for spec %s" % (event.filename))
+ elif len(entries) > 1:
+ syslog(LOG_ERR, "Cfg: Matched multiple entries for spec %s" % (event.filename))
+
+ if action == 'deleted':
+ syslog(LOG_INFO, "Cfg: Removing entry %s" % event.filename)
+ for entry in entries:
+ syslog(LOG_INFO, "Cfg: Removing entry %s" % (entry.name))
+ if entry in self.basefiles:
+ self.basefiles.remove(entry)
+ if entry in self.deltas:
+ self.deltas.remove(entry)
+ syslog(LOG_INFO, "Cfg: Entry deletion completed")
+ elif action in ['changed', 'exists', 'created']:
+ if action == 'changed':
+ syslog(LOG_INFO, "Cfg: File %s changed" % event.filename)
+ [entry.HandleEvent(event) for entry in entries]
+ else:
+ syslog(LOG_ERR, "Cfg: Unhandled Action %s for file %s" % (action, event.filename))
+
+ def GetConfigFile(self, entry, metadata):
+ '''Fetch config file from repository'''
+ name = entry.attrib['name']
+ filedata = ""
+ # first find basefile
+ try:
+ basefile = [bfile for bfile in self.basefiles if metadata.Applies(bfile)][-1]
+ except IndexError:
+ syslog(LOG_ERR, "Cfg: Failed to locate basefile for %s" % name)
+ raise PluginExecutionError, ('basefile', name)
+ filedata += basefile.data
+
+ for delta in [x for x in self.deltas if metadata.Applies(x)]:
+ # find applicable deltas
+ lines = filedata.split('\n')
+ if not lines[-1]:
+ lines = lines[:-1]
+ dlines = [dline for dline in delta.data.split('\n') if dline]
+ for line in dlines:
+ if line[0] == '-':
+ if line[1:] in lines:
+ lines.remove(line[1:])
+ else:
+ lines.append(line[1:])
+ filedata = "\n".join(lines) + "\n"
+
+ entry.attrib.update(self.metadata)
+ if self.paranoid:
+ entry.attrib['paranoid'] = 'true'
+ if self.metadata['encoding'] == 'base64':
+ entry.text = b2a_base64(filedata)
+ else:
+ entry.text = filedata
+
+class Cfg(Plugin):
+ '''This generator in the configuration file repository for bcfg2'''
+ __name__ = 'Cfg'
+ __version__ = '$Id$'
+ __author__ = 'bcfg-dev@mcs.anl.gov'
+ tempfile = regcompile("^.*~$|^.*\.swp")
+
+ def __init__(self, core, datastore):
+ Plugin.__init__(self, core, datastore)
+ self.entries = {}
+ self.Entries = {'ConfigFile':{}}
+ self.famID = {}
+ self.directories = []
+ self.AddDirectoryMonitor(self.data)
+ # eventually flush fam events here so that all entries built here
+ # ready to go
+
+ def AddDirectoryMonitor(self, name):
+ '''Add new directory to FAM structures'''
+ if name not in self.directories:
+ try:
+ stat(name)
+ except OSError:
+ self.LogError("Failed to open directory %s" % (name))
+ return
+ reqid = self.core.fam.AddMonitor(name, self)
+ self.famID[reqid] = name
+ self.directories.append(name)
+
+ def AddEntry(self, name, event):
+ '''Add new entry to FAM structures'''
+ try:
+ sdata = stat(name)[ST_MODE]
+ except OSError:
+ return
+
+ if S_ISDIR(sdata):
+ self.AddDirectoryMonitor(name)
+ else:
+ # file entries shouldn't contain path-to-repo
+ shortname = '/'+ '/'.join(name[len(self.data)+1:].split('/')[:-1])
+ repodir = '/' + '/'.join(name.split('/')[:-1])
+ if not self.entries.has_key(shortname):
+ self.entries[shortname] = ConfigFileEntry(shortname, repodir)
+ self.Entries['ConfigFile'][shortname] = self.entries[shortname].GetConfigFile
+ self.entries[shortname].AddEntry(name)
+ self.entries[shortname].HandleEvent(event)
+
+ def HandleEvent(self, event):
+ '''Handle FAM updates'''
+ action = event.code2str()
+ if self.tempfile.match(event.filename):
+ syslog(LOG_INFO, "Cfg: Suppressed event for file %s" % event.filename)
+ return
+ if event.filename[0] != '/':
+ filename = "%s/%s" % (self.famID[event.requestID], event.filename)
+ else:
+ filename = event.filename
+ configfile = filename[len(self.data):-(len(event.filename)+1)]
+
+ if ((action in ['exists', 'created']) and (filename != self.data)):
+ self.AddEntry(filename, event)
+ elif action == 'changed':
+ # pass the event down the chain to the ConfigFileEntry
+ if self.entries.has_key(configfile):
+ self.entries[configfile].HandleEvent(event)
+ else:
+ if filename != self.data:
+ self.AddEntry(filename, event)
+ else:
+ self.LogError("Ignoring event for %s"%(configfile))
+ elif action == 'deleted':
+ if self.entries.has_key(configfile):
+ self.entries[configfile].HandleEvent(event)
+ elif action in ['exists', 'endExist']:
+ pass
+ else:
+ self.LogError("Got unknown event %s %s:%s" % (action, event.requestID, event.filename))