#!/usr/bin/env python '''Bcfg2 Client''' __revision__ = '$Revision$' from getopt import getopt, GetoptError from os import popen, chmod, unlink, _exit from signal import signal, SIGINT from sys import argv from tempfile import mktemp from ConfigParser import ConfigParser, NoSectionError, NoOptionError from time import time from xmlrpclib import ServerProxy, Fault from lxml.etree import Element, XML, tostring def cb_sigint_handler(signum, frame): '''Exit upon CTRL-C''' _exit(1) def load_toolset(toolset, config, clientsetup): '''Import client toolset modules''' if toolset == 'debian': if clientsetup['verbose']: print 'Selected Debian Toolset...' mod = __import__("Bcfg2.Client.Debian", globals(), locals(), ['*']) return mod.Debian(config, clientsetup) elif toolset == 'rh': if setup['verbose']: print 'Selected RedHat Toolset...' mod = __import__("Bcfg2.Client.Redhat", globals(), locals(), ['*']) return mod.Redhat(config, clientsetup) elif toolset == 'solaris': if setup['verbose']: print "Selected Solaris Toolset" mod = __import__("Bcfg2.Client.Solaris", globals(), locals(), ['*']) return mod.Solaris(config, clientsetup) else: print "Got unsupported toolset %s from server." % (toolset) raise SystemExit, 1 def run_probe(probe): '''Execute probe''' ret = Element("probe-data", name=probe.attrib['name'], source=probe.attrib['source']) script = open(mktemp(), 'w+') script.write("#!%s\n"%(probe.attrib.get('interpreter', '/bin/sh'))) script.write(probe.text) script.close() chmod(script.name, 0755) ret.text = popen(script.name).read() unlink(script.name) return ret def dgetopt(arglist, opt, vopt): '''parse options into a dictionary''' ret = {} for optname in opt.values() + vopt.values(): ret[optname] = False gstr = "".join(opt.keys()) + "".join([optionkey + ':' for optionkey in vopt.keys()]) try: ginfo = getopt(arglist, gstr) except GetoptError, gerr: print gerr print "bcfg2 Usage:" for arg in opt.iteritems(): print " -%s %s" % arg for arg in vopt.iteritems(): print " -%s <%s>" % arg raise SystemExit, 1 for (gopt, garg) in ginfo[0]: option = gopt[1:] if opt.has_key(option): ret[opt[option]] = True else: ret[vopt[option]] = garg return ret if __name__ == '__main__': # parse command line options signal(SIGINT, cb_sigint_handler) options = {'v':'verbose', 'q':'quick', 'd':'debug', 'n':'dryrun', 'B':'build', 'P':'paranoid'} doptions = {'b':'bundle', 'f':'file', 'c':'cache', 'p':'profile', 'i':'image', 'r':'remove'} setup = dgetopt(argv[1:], options, doptions) timeinfo = Element("Times") comm = None if setup['file']: try: configfile = open(setup['file'], 'r') r = configfile.read() configfile.close() except IOError: print "Failed to read cached config file: %s" % (setup['file']) raise SystemExit, 1 else: cf = ConfigParser() cf.read('/etc/bcfg2.conf') location = cf.get("components", "bcfg2") proxy = ServerProxy(location) user = 'root' retries = 0 password = cf.get("communication", "password") # get probes start = time() for i in xrange(6): try: probedata = proxy.GetProbes(user, password) break except Fault, f: print "Got Fault from server", f break except: retries += 1 if i == 5: print "Failed to connect to server" raise SystemExit, 1 timeinfo.set('probefetch', str(time() - start)) probes = XML(probedata) # execute probes probeinfo = [run_probe(x) for x in probes.findall(".//probe")] # upload probe responses for i in xrange(6): try: proxy.RecvProbeData(user, password, probeinfo) break except Fault, f: print "Got Fault from server", f break except: retries += 1 if i == 5: print "Failed to connect to server" raise SystemExit, 1 cstart = time() for i in xrange(6): try: cfginfo = proxy.GetConfig(user, password, setup['image'], setup['profile']) break except Fault, f: print "Got Fault from server", f break except: retries += 1 if i == 5: print "Failed to connect to server" raise SystemExit, 1 timeinfo.set('config', str(time() - cstart )) if setup['cache']: try: open(setup['cache'], 'w').write(r) except IOError: print "failed to write config cache file %s" % (setup['cache']) pt = time() try: cfg = XML(cfginfo) except: print "Server returned unparseable config" raise SystemExit, 1 timeinfo.set('parse', str(time() - pt)) if cfg.tag == 'error': print "got error from server" raise SystemExit, 1 # Get toolset from server cfg_toolset = cfg.get('toolset') if setup['bundle']: c = Element("Configuration", version='2.0') for child in cfg.getroot().getchildren(): if ((child.tag == 'Bundle') and (child.attrib['name'] == setup['bundle'])): c.append(child) cfg = c # Create toolset handle client = load_toolset(cfg_toolset, cfg, setup) istart = time() # verify state client.Inventory() timeinfo.set('inventory', str(time() - istart)) correct = client.states.values().count(True) total = len(client.states.values()) istart = time() if ((correct < total) or client.pkgwork['remove']): if client.pkgwork['remove']: client.CondPrint('verbose', "Extra packages detected") # summarize current state client.CondPrint('verbose', "--> %s of %s config elements correct" % (correct, total)) # install incorrect aspects of configuration client.Install() client.CondPrint('verbose', "--> %s of %s config elements correct" % (client.states.values().count(True), total)) failed = [key for key, value in client.states.iteritems() if not value] if failed: client.CondPrint('verbose', "Failing Entries:") [client.CondPrint('verbose', "%s:%s" % (key.tag, key.get('name'))) for key in failed if key.tag != 'Package'] [client.CondPrint('verbose', "%s:%s-%s" % (key.tag, key.get('name'), key.get('version', 'unset'))) for key in failed if key.tag == 'Package'] else: client.CondPrint("verbose", "All entries correct") timeinfo.set('install', str(time() - istart)) timeinfo.set('total', str(time() - start)) if not setup['file']: # upload statistics m = Element("upload-statistics") stats = client.GenerateStats(__revision__) stats.append(timeinfo) m.append(stats) for i in xrange(6): try: stats.attrib['retries'] = str(retries) probedata = proxy.RecvStats(user, password, tostring(m)) break except Fault, f: print "Got Fault from server", f break except: retries += 1 if i == 5: print "Failed to connect to server" raise SystemExit, 1