#!/usr/bin/python -i '''This tool loads the Bcfg2 core into an interactive debugger''' __revision__ = '$Revision$' import copy, logging, lxml.etree, sys, cmd, time, tempfile, profile, pstats import Bcfg2.Logger, Bcfg2.Server.Core, os import Bcfg2.Server.Plugins.Metadata, Bcfg2.Server.Plugin import Bcfg2.Options logger = logging.getLogger('bcfg2-info') class dummyError(Exception): pass def printTabular(rows): '''print data in tabular format''' cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \ for index in range(len(rows[0]))]) fstring = (" %%-%ss |" * len(cmax)) % cmax fstring = ('|'.join([" %%-%ss "] * len(cmax))) % cmax print(fstring % rows[0]) print((sum(cmax) + (len(cmax) * 2) + (len(cmax) - 1)) * '=') for row in rows[1:]: print(fstring % row) def displayTrace(trace, num=80, sort=('time', 'calls')): stats = pstats.Stats(trace) stats.sort_stats('cumulative', 'calls', 'time') stats.print_stats(200) class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): def __init__(self, repo, plgs, passwd, encoding, event_debug): cmd.Cmd.__init__(self) try: Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd, encoding) if event_debug: self.fam.debug = True except Bcfg2.Server.Core.CoreInitError, msg: print("Core load failed because %s" % msg) raise SystemExit(1) self.prompt = '> ' self.cont = True self.fam.handle_events_in_interval(4) def do_loop(self): self.cont = True while self.cont: try: self.cmdloop() except SystemExit, val: raise except Bcfg2.Server.Plugin.PluginExecutionError: continue except dummyError: continue except: logger.error("command failure", exc_info=1) def do_debug(self, _): self.cont = False print("dropping to python interpreter; run loop.do_loop() to resume") raise dummyError def do_quit(self, _): """Exit program. Usage: [quit|exit]""" os._exit(0) do_EOF = do_quit do_exit = do_quit def do_help(self, _): '''print out usage info''' print 'Commands:' print 'build - build config for hostname, writing to filename' print 'buildall - build configs for all clients in directory' print 'buildfile - build config file for hostname (not written to disk)' print 'bundles - print out group/bundle information' print 'clients - print out client/profile information' print 'debug - shell out to native python interpreter' print 'event_debug - display filesystem events as they are processed' print 'generators - list current versions of generators' print 'groups - list groups' print 'help - print this text' print 'mappings - print generator mappings for optional type and name' print 'profile - profile a single bcfg2-info command' print 'quit' print 'showentries - show abstract configuration entries for a given host' print 'showclient - show metadata for given hosts' print 'update - process pending file events' print 'version - print version of this tool' def do_update(self, _): '''Process pending fs events''' self.fam.handle_events_in_interval(0.1) def do_version(self, _): '''print out code version''' print(__revision__) def do_build(self, args): '''build client configuration''' alist = args.split() path_force = False if '-f' in args: alist.remove('-f') path_force = True if len(alist) == 2: client, ofile = alist if not ofile.startswith('/tmp') and not path_force: print("Refusing to write files outside of /tmp without -f option") return output = open(ofile, 'w') data = lxml.etree.tostring(self.BuildConfiguration(client), encoding='UTF-8', xml_declaration=True, pretty_print=True) output.write(data) output.close() else: print('Usage: build [-f] ') def do_buildall(self, args): if len(args.split()) != 1: print("Usage: buildall ") return try: os.mkdir(args) except: pass for client in self.metadata.clients: self.do_build("%s %s/%s.xml" % (client, args, client)) def do_buildfile(self, args): '''build a config file for client''' if len(args.split()) == 2: fname, client = args.split() entry = lxml.etree.Element('ConfigFile', name=fname) metadata = self.build_metadata(client) self.Bind(entry, metadata) print(lxml.etree.tostring(entry, encoding="UTF-8", xml_declaration=True)) else: print('Usage: buildfile filename hostname') def do_bundles(self, _): '''print out group/bundle info''' data = [('Group', 'Bundles')] groups = list(self.metadata.groups.keys()) groups.sort() for group in groups: data.append((group, ','.join(self.metadata.groups[group][0]))) printTabular(data) def do_clients(self, _): '''print out client info''' data = [('Client', 'Profile')] clist = list(self.metadata.clients.keys()) clist.sort() for client in clist: data.append((client, self.metadata.clients[client])) printTabular(data) def do_generators(self, _): '''print out generator info''' for generator in self.generators: print(generator.__version__) def do_showentries(self, args): '''show abstract configuration entries for a given host''' arglen = len(args.split()) if arglen not in [2, 3]: print("Usage: showentries ") return client = args.split()[0] try: meta = self.build_metadata(client) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: print("Unable to find metadata for host %s" % client) return structures = self.GetStructures(meta) output = [('entrytype', 'name')] if arglen == 1: for item in structures: for child in item.getchildren(): output.append((child.tag, child.get('name'))) if arglen == 2: etype = args.split()[1] for item in structures: for child in item.getchildren(): if child.tag in [etype, "Bound%s" % etype]: output.append((child.tag, child.get('name'))) printTabular(output) def do_groups(self, _): '''print out group info''' data = [("Groups", "Profile", "Category", "Contains")] grouplist = list(self.metadata.groups.keys()) grouplist.sort() for group in grouplist: if group in self.metadata.profiles: prof = 'yes' else: prof = 'no' if group in self.metadata.categories: cat = self.metadata.categories[group] else: cat = '' gdata = [grp for grp in self.metadata.groups[group][1]] if group in gdata: gdata.remove(group) data.append((group, prof, cat, ','.join(gdata))) printTabular(data) def do_showclient(self, args): ''' print host metadata''' data = [('Client', 'Profile', "Groups", "Bundles")] if not len(args): print("Usage:\nshowclient ... ") return for client in args.split(): try: client_meta = self.build_metadata(client) except: print("Client %s not defined" % client) continue print "Hostname:\t", client_meta.hostname print "Profile:\t", client_meta.profile print "Groups:\t\t", list(client_meta.groups)[0] for grp in list(client_meta.groups)[1:]: print '\t\t%s' % grp if client_meta.bundles: print "Bundles:\t", list(client_meta.bundles)[0] for bnd in list(client_meta.bundles)[1:]: print '\t\t%s' % bnd if client_meta.connectors: print "Connector data" print "=" * 80 for conn in client_meta.connectors: if getattr(client_meta, conn): print "%s:\t" % (conn), getattr(client_meta, conn) print "=" * 80 def do_mappings(self, args): '''print out mapping info''' # dump all mappings unless type specified data = [('Plugin', 'Type', 'Name')] arglen = len(args.split()) for generator in self.generators: if arglen == 0: etypes = list(generator.Entries.keys()) else: etypes = [args.split()[0]] if arglen == 2: interested = [(etype, [args.split()[1]]) \ for etype in etypes] else: interested = [(etype, generator.Entries[etype]) \ for etype in etypes \ if etype in generator.Entries] for etype, names in interested: for name in [name for name in names if name in \ generator.Entries.get(etype, {})]: data.append((generator.name, etype, name)) printTabular(data) def do_event_debug(self, args): self.fam.debug = True def do_cfgdebug(self, args): try: meta = self.build_metadata(args) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: print("Unable to find metadata for host %s" % client) return structures = self.GetStructures(meta) for clist in [struct.findall('ConfigFile') for struct in structures]: for cfile in clist: if cfile.get('name') in self.plugins['Cfg'].Entries['ConfigFile']: cset = self.plugins['Cfg'].entries[cfile.get('name')] cand = cset.get_matching(meta) fields = ['all', 'group'] while len(cand) > 1 and fields: field = fields.pop(0) [cand.remove(c) for c in cand[:] if getattr(c.specific, field)] if len(cand) != 1: sys.stderr.write("Entry %s failed" % cfile.get('name')) continue print(cand[0].name) def do_profile(self, arg): tracefname = tempfile.mktemp() p = profile.Profile() p.runcall(self.onecmd, arg) displayTrace(p) def Run(self, args): if args: self.onecmd(" ".join(args)) os._exit(0) else: self.do_loop() if __name__ == '__main__': Bcfg2.Logger.setup_logging('bcfg2-info', to_syslog=False) optinfo = { 'configfile': Bcfg2.Options.CFILE, 'help': Bcfg2.Options.HELP, } optinfo.update({'repo': Bcfg2.Options.SERVER_REPOSITORY, 'plugins': Bcfg2.Options.SERVER_PLUGINS, 'password': Bcfg2.Options.SERVER_PASSWORD, 'event debug': Bcfg2.Options.DEBUG, 'profile': Bcfg2.Options.CORE_PROFILE, 'encoding': Bcfg2.Options.ENCODING}) setup = Bcfg2.Options.OptionParser(optinfo) setup.parse(sys.argv[1:]) print(setup) if setup['profile']: prof = profile.Profile() loop = prof.runcall(infoCore, setup['repo'], setup['plugins'], setup['password'], setup['encoding'], setup['event debug']) displayTrace(prof) else: loop = infoCore(setup['repo'], setup['plugins'], setup['password'], setup['encoding'], setup['event debug']) loop.Run(setup['args'])