From 9e31f1dd76c29d99fb5a16f0a2d6752cf5ead1c9 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Mon, 24 Mar 2008 04:15:41 +0000 Subject: Rework bcfg2-admin pull - forward port Cfg and SSHbase support - reimplement admin mode - add verbose flag, and implement initial interactive mode, also force mode git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@4446 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Server/Plugins/Cfg.py | 144 ++++++++++++++++++-------------------- src/lib/Server/Plugins/SSHbase.py | 31 +++----- 2 files changed, 79 insertions(+), 96 deletions(-) (limited to 'src/lib/Server/Plugins') diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py index aebce6188..f3e485517 100644 --- a/src/lib/Server/Plugins/Cfg.py +++ b/src/lib/Server/Plugins/Cfg.py @@ -1,8 +1,7 @@ '''This module implements a config file repository''' __revision__ = '$Revision$' -import binascii, difflib, logging, os, re, tempfile, \ - xml.sax.saxutils, Bcfg2.Server.Plugin, lxml.etree +import binascii, logging, os, re, tempfile, Bcfg2.Server.Plugin logger = logging.getLogger('Bcfg2.Plugins.Cfg') @@ -53,13 +52,15 @@ class CfgEntry(object): if entry.get('encoding') == 'base64': entry.text = binascii.b2a_base64(self.data) else: - entry.text = self.data + entry.text = self.data + if not entry.text: + entry.set('empty', 'true') class CfgMatcher: def __init__(self, fname): name = re.escape(fname) - self.basefile_reg = re.compile('^%s(|\\.H_(?P\S+)|.G(?P\d+)_(?P\S+))$' % name) - self.delta_reg = re.compile('^%s(|\\.H_(?P\S+)|\\.G(?P\d+)_(?P\S+))\\.(?P(cat|diff))$' % fname) + self.basefile_reg = re.compile('^(?P%s)(|\\.H_(?P\S+)|.G(?P\d+)_(?P\S+))$' % name) + self.delta_reg = re.compile('^(?P%s)(|\\.H_(?P\S+)|\\.G(?P\d+)_(?P\S+))\\.(?P(cat|diff))$' % fname) self.cat_count = fname.count(".cat") self.diff_count = fname.count(".diff") @@ -76,28 +77,66 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): def sort_by_specific(self, one, other): return cmp(one.specific, other.specific) - - def bind_entry(self, entry, metadata): + + def get_pertinent_entries(self, metadata): + '''return a list of all entries pertinent to a client => [base, delta1, delta2]''' matching = [ent for ent in self.entries.values() if \ ent.specific.matches(metadata)] - if [ent for ent in matching if ent.specific.delta]: - self.bind_info_to_entry(entry, metadata) - matching.sort(self.sort_by_specific) - base = min([matching.index(ent) for ent in matching - if not ent.specific.delta]) - used = matching[:base+1] - used.reverse() - # used is now [base, delta1, delta2] - basefile = used.pop() - data = basefile.data - for delta in used: - data = process_delta(data, delta) - if entry.get('encoding') == 'base64': - entry.text = binascii.b2a_base64(data) - else: - entry.text = data + matching.sort(self.sort_by_specific) + base = min([matching.index(ent) for ent in matching + if not ent.specific.delta]) + used = matching[:base+1] + used.reverse() + return used + + def bind_entry(self, entry, metadata): + self.bind_info_to_entry(entry, metadata) + used = self.get_pertinent_entries(metadata) + basefile = used.pop() + data = basefile.data + for delta in used: + data = process_delta(data, delta) + if entry.get('encoding') == 'base64': + entry.text = binascii.b2a_base64(data) else: - Bcfg2.Server.Plugin.EntrySet.bind_entry(self, entry, metadata) + entry.text = data + + def list_accept_choices(self, metadata): + '''return a list of candidate pull locations''' + used = self.get_pertinent_entries(metadata) + if len(used) > 1: + return [] + return [used[0].specific] + + def build_filename(self, specific): + bfname = self.path + '/' + self.path.split('/')[-1] + if specific.all: + return bfname + elif specific.group: + return "%s.G%d_%s" % (bfname, specific.group, specific.prio) + elif specific.hostname: + return "%s.H_%s" % (bfname, specific.hostname) + + def write_update(self, specific, new_entry, log): + name = self.build_filename(specific) + open(name, 'w').write(new_entry['text']) + if log: + logger.info("Wrote file %s" % name) + badattr = [attr for attr in ['owner', 'group', 'perms'] if attr in new_entry] + if badattr: + if hasattr(self.entries[name.split('/')[-1]], 'infoxml'): + print "InfoXML support not yet implemented" + return + metadata_updates = {} + metadata_updates.update(self.metadata) + for attr in badattr: + metadata_updates[attr] = new_entry.get('attr') + infofile = open(self.path + '/:info', 'w') + for x in metadata_updates.iteritems(): + infofile.write("%s: %s\n" % x) + infofile.close() + if log: + logger.info("Wrote file %s" % infofile.name) class Cfg(Bcfg2.Server.Plugin.GroupSpool): '''This generator in the configuration file repository for bcfg2''' @@ -108,56 +147,9 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool): es_cls = CfgEntrySet es_child_cls = CfgEntry - def AcceptEntry(self, meta, _, entry_name, diff, fulldata, metadata_updates={}): - '''per-plugin bcfg2-admin pull support''' - if metadata_updates: - if hasattr(self.Entries['ConfigFile'][entry_name], 'infoxml'): - print "InfoXML support not yet implemented" - elif raw_input("Should metadata updates apply to all hosts? (n/Y) ") in ['Y', 'y']: - self.entries[entry_name].metadata.update(metadata_updates) - infofile = open(self.entries[entry_name].repopath + '/:info', 'w') - for x in self.entries[entry_name].metadata.iteritems(): - infofile.write("%s: %s\n" % x) - infofile.close() - if not diff and not fulldata: - raise SystemExit, 0 - - hsq = "Found host-specific file %s; Should it be updated (n/Y): " - repo_vers = lxml.etree.Element('ConfigFile', name=entry_name) - self.Entries['ConfigFile'][entry_name](repo_vers, meta) - repo_curr = repo_vers.text - # find the file fragment - basefile = [frag for frag in \ - self.entries[entry_name].fragments \ - if frag.applies(meta)][-1] - gsq = "Should this change apply to all hosts effected by file %s? (N/y): " % (basefile.name) - if ".H_%s" % (meta.hostname) in basefile.name: - answer = raw_input(hsq % basefile.name) - else: - answer = raw_input(gsq) - - if answer in ['Y', 'y']: - print "writing file, %s" % basefile.name - if fulldata: - newdata = fulldata - else: - newdata = '\n'.join(difflib.restore(diff.split('\n'), 1)) - open(basefile.name, 'w').write(newdata) - return + def AcceptChoices(self, entry, metadata): + return self.entries[entry.get('name')].list_accept_choices(metadata) + + def AcceptPullData(self, specific, new_entry, log): + return self.entries[new_entry.get('name')].write_update(specific, new_entry, log) - if ".H_%s" % (meta.hostname) in basefile.name: - raise SystemExit, 1 - # figure out host-specific filename - reg = re.compile("(.*)\.G\d+.*") - if reg.match(basefile.name): - newname = reg.match(basefile.name).group(1) + ".H_%s" % (meta.hostname) - else: - newname = basefile.name + ".H_%s" % (meta.hostname) - print "This file will be installed as file %s" % newname - if raw_input("Should it be installed? (N/y): ") in ['Y', 'y']: - print "writing file, %s" % newname - if fulldata: - newdata = fulldata - else: - newdata = '\n'.join(difflib.restore(diff.split('\n'), 1)) - open(newname, 'w').write(newdata) diff --git a/src/lib/Server/Plugins/SSHbase.py b/src/lib/Server/Plugins/SSHbase.py index 4254ad6d9..89767cf85 100644 --- a/src/lib/Server/Plugins/SSHbase.py +++ b/src/lib/Server/Plugins/SSHbase.py @@ -1,15 +1,9 @@ '''This module manages ssh key files for bcfg2''' __revision__ = '$Revision$' -import binascii, difflib, os, socket, xml.sax.saxutils +import binascii, os, socket import Bcfg2.Server.Plugin -def update_file(path, diff): - '''Update file at path using diff''' - newdata = '\n'.join(difflib.restore(diff.split('\n'), 1)) - print "writing file, %s" % path - open(path, 'w').write(newdata) - class SSHbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): '''The sshbase generator manages ssh host keys (both v1 and v2) for hosts. It also manages the ssh_known_hosts file. It can @@ -190,17 +184,14 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): except OSError: self.logger.error("Failed to unlink temporary ssh keys") - def AcceptEntry(self, meta, _, entry_name, diff, fulldata, metadata_updates={}): - '''per-plugin bcfg2-admin pull support''' - filename = "%s/%s.H_%s" % (self.data, entry_name.split('/')[-1], - meta.hostname) - print "This file will be installed as file %s" % filename - if raw_input("Should it be installed? (N/y): ") in ['Y', 'y']: - print "writing file, %s" % filename - if fulldata: - newdata = fulldata - else: - newdata = '\n'.join(difflib.restore(diff.split('\n'), 1)) - open(filename, 'w').write(newdata) + def AcceptChoices(self, _, metadata): + return Bcfg2.Server.Plugin.Specificity(hostname=metadata.hostname) - + def AcceptPullData(self, specific, entry, log): + '''per-plugin bcfg2-admin pull support''' + # specific will always be host specific + filename = "%s/%s.H_%s" % (self.data, entry['name'].split('/')[-1], + specific.hostname) + open(filename, 'w').write(entry['text']) + if log: + print "Wrote file %s" % filename -- cgit v1.2.3-1-g7c22