#!/usr/bin/env python '''Bcfg2 Client''' __revision__ = '$Revision$' import logging, os, signal, tempfile, time, xmlrpclib import Bcfg2.Options, Bcfg2.Client.XML, Bcfg2.Client.Frame try: import Bcfg2.Client.Proxy, Bcfg2.Logging except KeyError: print "Could not read options from configuration file" raise SystemExit, 1 def cb_sigint_handler(signum, frame): '''Exit upon CTRL-C''' os._exit(1) class Client: ''' The main bcfg2 client class ''' def __init__(self): self.toolset = None self.config = None optinfo = { # 'optname': (('-a', argdesc, optdesc), # env, cfpath, default, boolean)), 'verbose':(('-v', False,"enable verbose output"), False, False, False, True), 'extra':(('-e', False,"enable extra entry detailed output"), False, False, False, True), 'quick':(('-q', False, "disable some checksum verification"), False, False, False, True), 'debug':(('-d', False, "enable debugging output"), False, False, False, True), 'drivers':(('-D', ',', "Specify tool driver set"), False, False, False, False), 'dryrun':(('-n', False, "do not actually change the system"), False, False, False, True), 'build': (('-B', False, "run in build mode"), False, False, False, True), 'paranoid':(('-P', False, "make automatic backups of config files"), False, False, False, True), 'bundle':(('-b', '', "only configure the given bundle"), False, False, False, False), 'file': (('-f', "", "configure from a file rather than querying the server"), False, False, False, False), 'interactive': (('-I', False, "prompt the user for each change"), False, False, False, True), 'cache': (('-c', "", "store the configuration in a file"), False, False, False, False), 'profile': (('-p', '', "assert the given profile for the host"), False, False, False, False), 'remove': (('-r', '(packages|services|all)', "force removal of additional configuration items"), False, False, False, False), 'help': (('-h', False, "print this help message"), False, False, False, True), 'setup': (('-C', '', "use given config file (default /etc/bcfg2.conf)"), False, False, '/etc/bcfg2.conf', False), 'server': (('-S', '', 'the server hostname to connect to'), False, ('components', 'bcfg2'), 'https://localhost:6789', False), 'user': (('-u', '', 'the user to provide for authentication'), False, ('communication', 'user'), 'root', False), 'password': (('-x', '', 'the password to provide for authentication'), False, ('communication', 'password'), 'password', False), 'retries': (('-R', '', 'the number of times to retry network communication'), False, ('communication', 'retries'), '3', False), 'kevlar': (('-k', False, "run in kevlar (bulletproof) mode"), False, False, False, True), } self.setup = Bcfg2.Options.OptionParser('bcfg2', optinfo).parse() level = 30 if self.setup['verbose']: level = 20 if self.setup['debug']: level = 0 Bcfg2.Logging.setup_logging('bcfg2', to_syslog=False, level=level) self.logger = logging.getLogger('bcfg2') self.logger.debug(self.setup) if self.setup['remove'] not in [False, 'all', 'services', 'packages']: self.logger.error("Got unknown argument %s for -r" % (self.setup['remove'])) if (self.setup["file"] != False) and (self.setup["cache"] != False): print "cannot use -f and -c together" raise SystemExit, 1 def run_probe(self, probe): '''Execute probe''' name = probe.get('name') ret = Bcfg2.Client.XML.Element("probe-data", name=name, source=probe.get('source')) try: script = open(tempfile.mktemp(), 'w+') try: script.write("#!%s\n" % (probe.attrib.get('interpreter', '/bin/sh'))) script.write(probe.text) script.close() os.chmod(script.name, 0755) ret.text = os.popen(script.name).read().strip() finally: os.unlink(script.name) except: self.logger.error("Failed to execute probe: %s" % (name), exc_info=1) raise SystemExit, 1 return ret def fatal_error(self, message): '''Signal a fatal error''' self.logger.error("Fatal error: %s" % (message)) raise SystemExit, 1 def get_config(self): '''Either download the config from the server or read it from file''' def run(self): ''' Perform client execution phase ''' times = {} # begin configuration times['start'] = time.time() if self.setup['file']: # read config from file try: self.logger.debug("reading cached configuration from %s" % (self.setup['file'])) configfile = open(self.setup['file'], 'r') rawconfig = configfile.read() configfile.close() except IOError: self.fatal_error("failed to read cached configuration from: %s" % (self.setup['file'])) else: # retrieve config from server proxy = Bcfg2.Client.Proxy.bcfg2() if self.setup['profile']: try: proxy.AssertProfile(self.setup['profile']) except xmlrpclib.Fault: self.fatal_error("Failed to set client profile") try: probe_data = proxy.GetProbes() except xmlrpclib.Fault, flt: self.logger.error("Failed to download probes from bcfg2") self.logger.error(flt.faultString) raise SystemExit, 1 times['probe_download'] = time.time() try: probes = Bcfg2.Client.XML.XML(probe_data) except Bcfg2.Client.XML.ParseError, syntax_error: self.fatal_error( "server returned invalid probe requests: %s" % (syntax_error)) # execute probes try: probedata = Bcfg2.Client.XML.Element("ProbeData") [probedata.append(self.run_probe(probe)) for probe in probes.findall(".//probe")] except: self.logger.error("Failed to Execute probes") raise SystemExit, 1 if len(probes.findall(".//probe")) > 0: try: # upload probe responses proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata)) except: self.logger.error("Failed to upload probe data", exc_info=1) raise SystemExit, 1 times['probe_upload'] = time.time() try: rawconfig = proxy.GetConfig() except xmlrpclib.Fault: self.logger.error("Failed to download configuration from bcfg2") raise SystemExit, 2 times['config_download'] = time.time() if self.setup['cache']: try: open(self.setup['cache'], 'w').write(rawconfig) os.chmod(self.setup['cache'], 33152) except IOError: self.logger.warning("failed to write config cache file %s" % (self.setup['cache'])) times['caching'] = time.time() try: self.config = Bcfg2.Client.XML.XML(rawconfig) except Bcfg2.Client.XML.ParseError, syntax_error: self.fatal_error("the configuration could not be parsed: %s" % (syntax_error)) times['config_parse'] = time.time() if self.config.tag == 'error': self.fatal_error("server error: %s" % (self.config.text)) self.tools = Bcfg2.Client.Frame.Frame(self.config, self.setup, times) self.tools.Execute() if not self.setup['file']: # upload statistics feedback = self.tools.GenerateStats() try: proxy.RecvStats(Bcfg2.Client.XML.tostring(feedback)) except xmlrpclib.Fault: self.logger.error("Failed to upload configuration statistics") raise SystemExit, 2 if __name__ == '__main__': signal.signal(signal.SIGINT, cb_sigint_handler) client = Client() client.run()