#!/usr/bin/python
'''This tool loads the Bcfg2 core into an interactive debugger'''
__revision__ = '$Revision$'
from code import InteractiveConsole
import cmd
import errno
import logging
import lxml.etree
import os
import profile
import pstats
import sys
import tempfile
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 debug_fcacher:
"Cache the stdout text so we can analyze it before returning it"
def __init__(self): self.reset()
def reset(self): self.out = []
def write(self, line): self.out.append(line)
def flush(self):
output = '\n'.join(self.out)
self.reset()
return output
class debug_shell(InteractiveConsole):
"Wrapper around Python that can filter input/output to the shell"
def __init__(self, mylocals):
self.stdout = sys.stdout
self.cache = debug_fcacher()
InteractiveConsole.__init__(self, mylocals)
def get_output(self): sys.stdout = self.cache
def return_output(self): sys.stdout = self.stdout
def push(self, line):
self.get_output()
# you can filter input here by doing something like
# line = filter(line)
rc = InteractiveConsole.push(self, line)
self.return_output()
output = self.cache.flush()
# you can filter the output here by doing something like
# output = filter(output)
sys.stdout.write(output)
return rc
class ConfigFileNotBuilt(Exception):
''' Thrown when ConfigFile 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 ... 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 ConfigFileNotBuilt(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, _):
self.cont = False
print("dropping to python interpreter; press ^D to resume")
sh = debug_shell(locals())
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 - build config for hostname, writing to filename'
print 'builddir - build config for hostname, writing separate files to dirname'
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]