diff options
Diffstat (limited to 'build/scripts-2.7/bcfg2-info')
-rwxr-xr-x | build/scripts-2.7/bcfg2-info | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/build/scripts-2.7/bcfg2-info b/build/scripts-2.7/bcfg2-info new file mode 100755 index 000000000..9721122f3 --- /dev/null +++ b/build/scripts-2.7/bcfg2-info @@ -0,0 +1,468 @@ +#!/usr/bin/python + +"""This tool loads the Bcfg2 core into an interactive debugger.""" +__revision__ = '$Revision$' + +from code import InteractiveConsole +import cmd +import errno +import getopt +import logging +import lxml.etree +import os +import sys +import tempfile + +try: + import profile + import pstats + have_profile = True +except: + have_profile = False + +import Bcfg2.Logger +import Bcfg2.Options +import Bcfg2.Server.Core +import Bcfg2.Server.Plugins.Metadata +import Bcfg2.Server.Plugin + +logger = logging.getLogger('bcfg2-info') + +class dummyError(Exception): + pass + +class FileNotBuilt(Exception): + """Thrown when File entry contains no content.""" + def __init__(self, value): + Exception.__init__(self) + self.value = value + def __str__(self): + return repr(self.value) + +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) + +def write_config_file(outputdir, cfg): + """Store file content of an <ConfigFile name='/path/to/file' ...>...</ConfigFile> entry + in the appropriate directory under the output directory. + """ + name = cfg.get('name') + + # directory creation + try: + os.makedirs(os.path.dirname(outputdir + name)) + except OSError, err: + if err.errno != errno.EEXIST: + raise + except: + raise + + # write config file + config_file = open(outputdir + name, "w") + try: + config_file.write(cfg.text) + except: # plugin throw an exception and therefore there is no content => None + raise FileNotBuilt(name) + config_file.close() + +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('Welcome to bcfg2-info\n' + 'Type "help" for more information') + except SystemExit, val: + raise + except Bcfg2.Server.Plugin.PluginExecutionError: + continue + except KeyboardInterrupt: + print("Ctrl-C pressed exiting...") + self.do_exit([]) + except dummyError: + continue + except: + logger.error("command failure", exc_info=1) + + def do_debug(self, args): + try: + opts, _ = getopt.getopt(args.split(), 'nf:') + except: + print "Usage: debug [-n] [-f <command list>]" + return + self.cont = False + scriptmode = False + interactive = True + for opt in opts: + if opt[0] == '-f': + scriptmode = True + spath = opt[1] + elif opt[0] == '-n': + interactive = False + sh = InteractiveConsole(locals()) + if scriptmode: + for command in [c.strip() for c in open(spath).readlines()]: + if command: + sh.push(command) + if interactive: + print("dropping to python interpreter; press ^D to resume") + try: + import IPython + shell = IPython.Shell.IPShell(argv=[], user_ns=locals()) + shell.mainloop() + except ImportError: + sh.interact() + + def do_quit(self, _): + """ + Exit program. + Usage: [quit|exit] + """ + for plugin in self.plugins.values(): + plugin.shutdown() + os._exit(0) + + do_EOF = do_quit + do_exit = do_quit + + def do_help(self, _): + """Print out usage info.""" + print 'Commands:' + print 'build <hostname> <filename> - build config for hostname, writing to filename' + print 'builddir <hostname> <dirname> - build config for hostname, writing separate files to dirname' + print 'buildall <directory> - build configs for all clients in directory' + print 'buildfile <filename> <hostname> - 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 list of available commands' + print 'mappings <type*> <name*> - print generator mappings for optional type and name' + print 'profile <command> <args> - profile a single bcfg2-info command' + print 'quit - Exit the bcfg2-info command line' + print 'showentries <hostname> <type> - show abstract configuration entries for a given host' + print 'showclient <client1> <client2> - 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] <hostname> <output file>') + + def help_builddir(self): + """Display help for builddir command.""" + print('Usage: builddir [-f] <hostname> <output dir>') + print('') + print('Generates a config for client <hostname> and writes the') + print('individual configuration files out separately in a tree') + print('under <output dir>. The <output dir> directory must be') + print('rooted under /tmp unless the -f argument is provided, in') + print('which case it can be located anywhere.') + print('') + print('NOTE: Currently only handles ConfigFile entries and writes') + print('all content with the default owner and permissions. These') + print('could be much more permissive than would be created by the') + print('bcfg2 client itself.') + + def do_builddir(self, args): + """Build client configuration as separate files within a dir.""" + alist = args.split() + path_force = False + if '-f' in args: + alist.remove('-f') + path_force = True + if len(alist) == 2: + client, odir = alist + if not odir.startswith('/tmp') and not path_force: + print("Refusing to write files outside of /tmp without -f option") + return + client_config = self.BuildConfiguration(client) + if client_config.tag == 'error': + print("Building client configuration failed.") + return + + # handle <Path type='file'> entries + for configfile in [cfile for cfile in client_config.findall(".//Path[@type = 'file']")]: + try: + write_config_file(odir, configfile) + except FileNotBuilt, ex: + print("Warning: No file content generated for ConfigFile %s!" % ex) + pass + except Exception, ex: + print("unknown error, I give up: %s" %ex) + return + + print("Config for %s written to %s" % (client, odir)) + + else: + print('Error: Incorrect number of parameters') + self.help_builddir() + + def do_buildall(self, args): + if len(args.split()) != 1: + print("Usage: buildall <directory>") + 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('Path', type='file', 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 [1, 2]: + print("Usage: showentries <hostname> <type>") + 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 <client> ... <clientN>") + 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('Path') 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): + if not have_profile: + print("Profiling functionality not available") + return + 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:]) + if setup['profile'] and have_profile: + prof = profile.Profile() + loop = prof.runcall(infoCore, setup['repo'], setup['plugins'], + setup['password'], setup['encoding'], + setup['event debug']) + displayTrace(prof) + else: + if setup['profile']: + print("Profiling functionality not available") + loop = infoCore(setup['repo'], setup['plugins'], setup['password'], + setup['encoding'], setup['event debug']) + + loop.Run(setup['args']) |